@mirascript/monaco 0.1.87 → 0.1.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/basic/highlighter-manager.d.ts +7 -0
  2. package/dist/basic/highlighter-manager.d.ts.map +1 -0
  3. package/dist/basic/index.js +2 -2
  4. package/dist/basic/language-configuration.d.ts.map +1 -1
  5. package/dist/basic/textmate-to-monaco.d.ts +3 -0
  6. package/dist/basic/textmate-to-monaco.d.ts.map +1 -0
  7. package/dist/basic/tokens-provider.d.ts.map +1 -1
  8. package/dist/{chunk-JOHUNWDB.js → chunk-HULIQQS3.js} +6 -2
  9. package/dist/{chunk-JOHUNWDB.js.map → chunk-HULIQQS3.js.map} +1 -1
  10. package/dist/{chunk-6QSF4YTT.js → chunk-NW3ZHO3C.js} +70 -91
  11. package/dist/chunk-NW3ZHO3C.js.map +6 -0
  12. package/dist/contribute.d.ts +5 -1
  13. package/dist/contribute.d.ts.map +1 -1
  14. package/dist/index.js +7 -6
  15. package/dist/index.js.map +1 -1
  16. package/dist/lsp/index.d.ts +1 -1
  17. package/dist/lsp/index.d.ts.map +1 -1
  18. package/dist/lsp/index.js +252 -241
  19. package/dist/lsp/index.js.map +3 -3
  20. package/dist/lsp/providers/completion-item-provider/common-global-suggestions.d.ts +5 -0
  21. package/dist/lsp/providers/completion-item-provider/common-global-suggestions.d.ts.map +1 -0
  22. package/dist/lsp/providers/completion-item-provider/complete-fields.d.ts +5 -0
  23. package/dist/lsp/providers/completion-item-provider/complete-fields.d.ts.map +1 -0
  24. package/dist/lsp/providers/completion-item-provider/complete-global.d.ts +5 -0
  25. package/dist/lsp/providers/completion-item-provider/complete-global.d.ts.map +1 -0
  26. package/dist/lsp/providers/completion-item-provider/complete-local.d.ts +5 -0
  27. package/dist/lsp/providers/completion-item-provider/complete-local.d.ts.map +1 -0
  28. package/dist/lsp/providers/{completion-item-provider.d.ts → completion-item-provider/index.d.ts} +3 -11
  29. package/dist/lsp/providers/completion-item-provider/index.d.ts.map +1 -0
  30. package/dist/lsp/providers/completion-item-provider/interface.d.ts +16 -0
  31. package/dist/lsp/providers/completion-item-provider/interface.d.ts.map +1 -0
  32. package/dist/lsp/providers/completion-item-provider/utils.d.ts +11 -0
  33. package/dist/lsp/providers/completion-item-provider/utils.d.ts.map +1 -0
  34. package/dist/lsp/utils.d.ts +0 -2
  35. package/dist/lsp/utils.d.ts.map +1 -1
  36. package/package.json +6 -6
  37. package/src/basic/highlighter-manager.ts +38 -0
  38. package/src/basic/language-configuration.ts +16 -13
  39. package/src/basic/textmate-to-monaco.ts +49 -0
  40. package/src/basic/tokens-provider.ts +18 -127
  41. package/src/contribute.ts +5 -1
  42. package/src/index.ts +6 -6
  43. package/src/lsp/index.ts +1 -1
  44. package/src/lsp/providers/completion-item-provider/common-global-suggestions.ts +155 -0
  45. package/src/lsp/providers/completion-item-provider/complete-fields.ts +64 -0
  46. package/src/lsp/providers/completion-item-provider/complete-global.ts +82 -0
  47. package/src/lsp/providers/completion-item-provider/complete-local.ts +57 -0
  48. package/src/lsp/providers/completion-item-provider/index.ts +141 -0
  49. package/src/lsp/providers/completion-item-provider/interface.ts +17 -0
  50. package/src/lsp/providers/completion-item-provider/utils.ts +61 -0
  51. package/src/lsp/utils.ts +0 -13
  52. package/dist/chunk-6QSF4YTT.js.map +0 -6
  53. package/dist/lsp/providers/completion-item-provider.d.ts.map +0 -1
  54. package/src/lsp/providers/completion-item-provider.ts +0 -536
@@ -0,0 +1,49 @@
1
+ const REMAP_PREFIXES: ReadonlyArray<[string, string]> = [
2
+ ['constant.character.escape.', 'string.escape.'],
3
+ ['constant.numeric.', 'number.'],
4
+ ['constant.language.', 'keyword.'],
5
+ ['support.variable.', 'variable.'],
6
+ ['entity.name.type.', 'type.'],
7
+ ['support.type.', 'type.'],
8
+ ['entity.name.namespace.', 'namespace.'],
9
+ ['entity.name.function.', 'function.'],
10
+ ];
11
+
12
+ /** Remap scope */
13
+ function remapScope(scope: string): string {
14
+ for (const [prefix, remap] of REMAP_PREFIXES) {
15
+ if (scope.startsWith(prefix)) {
16
+ return remap + scope.slice(prefix.length);
17
+ }
18
+ }
19
+ return scope;
20
+ }
21
+
22
+ const STYLED_SCOPE_PREFIXES = [
23
+ 'invalid.',
24
+ 'comment.',
25
+ 'string.',
26
+ 'keyword.',
27
+ 'constant.',
28
+ 'variable.',
29
+ 'entity.',
30
+ 'storage.',
31
+ 'support.',
32
+ 'markup.',
33
+ 'meta.embedded.',
34
+ ];
35
+ /** Is styled scope */
36
+ function isStyledScope(scope: string): boolean {
37
+ return STYLED_SCOPE_PREFIXES.some((prefix) => scope.startsWith(prefix));
38
+ }
39
+
40
+ /** Select the deepest scope that a native Monaco theme can style. */
41
+ export function textmateScopesToMonaco(scopes: readonly string[]): string {
42
+ let fallback;
43
+ for (let index = scopes.length - 1; index >= 0; index -= 1) {
44
+ const scope = scopes[index]!;
45
+ fallback ||= scope;
46
+ if (isStyledScope(scope)) return remapScope(scope);
47
+ }
48
+ return fallback ? remapScope(fallback) : 'source.mira';
49
+ }
@@ -1,135 +1,18 @@
1
- import type { HighlighterCore, Grammar } from '@shikijs/core';
1
+ import type { Grammar } from '@shikijs/core';
2
2
  import type { StateStack } from '@shikijs/vscode-textmate';
3
3
  import { languages, type IDisposable } from '../monaco-api.js';
4
4
  import { CONTRIBUTE_IDS } from '../contribute.js';
5
+ import { getHighlighter, getInitialState } from './highlighter-manager.js';
6
+ import { textmateScopesToMonaco } from './textmate-to-monaco.js';
5
7
 
6
8
  const TOKENIZE_MAX_LINE_LENGTH = 20000;
7
9
  const TOKENIZE_TIME_LIMIT = 500;
8
10
 
9
- const REMAP_PREFIXES: ReadonlyArray<[string, string]> = [
10
- ['constant.character.escape.', 'string.escape.'],
11
- ['constant.numeric.', 'number.'],
12
- ['constant.language.', 'keyword.'],
13
- ['support.variable.', 'variable.'],
14
- ['entity.name.type.', 'type.'],
15
- ['support.type.', 'type.'],
16
- ['entity.name.namespace.', 'namespace.'],
17
- ['entity.name.function.', 'function.'],
18
- ];
19
-
20
- /** Remap scope */
21
- function remapScope(scope: string): string {
22
- for (const [prefix, remap] of REMAP_PREFIXES) {
23
- if (scope.startsWith(prefix)) {
24
- return remap + scope.slice(prefix.length);
25
- }
26
- }
27
- return scope;
28
- }
29
-
30
- const STYLED_SCOPE_PREFIXES = [
31
- 'invalid.',
32
- 'comment.',
33
- 'string.',
34
- 'keyword.',
35
- 'constant.',
36
- 'variable.',
37
- 'entity.',
38
- 'storage.',
39
- 'support.',
40
- 'markup.',
41
- ];
42
- /** Is styled scope */
43
- function isStyledScope(scope: string): boolean {
44
- return STYLED_SCOPE_PREFIXES.some((prefix) => scope.startsWith(prefix));
45
- }
46
-
47
- /** Select the deepest scope that a native Monaco theme can style. */
48
- function tokenScope(scopes: string[]): string {
49
- let embedded = scopes.findLastIndex((scope) => scope.startsWith('meta.embedded.'));
50
- if (embedded === -1) {
51
- embedded = 0;
52
- }
53
- let fallback;
54
- for (let index = scopes.length - 1; index >= embedded; index -= 1) {
55
- const scope = scopes[index]!;
56
- fallback ||= scope;
57
- if (isStyledScope(scope)) return remapScope(scope);
58
- if (scope.startsWith('meta.embedded.')) break;
59
- }
60
- return fallback ? remapScope(fallback) : 'source.mira';
61
- }
62
-
63
- /** Shared instance of highlighter. */
64
- class HighlighterManager implements IDisposable {
65
- private highlighterPromise: Promise<HighlighterCore> | null = null;
66
- private INITIAL!: StateStack;
67
- /**
68
- * Load the shared highlighter instance.
69
- */
70
- private async loadHighlighter(): Promise<HighlighterCore> {
71
- const [
72
- { createHighlighterCore },
73
- { createJavaScriptRegexEngine },
74
- { INITIAL },
75
- { mirascript, mirascriptDoc, mirascriptTemplate },
76
- ] = await Promise.all([
77
- import('@shikijs/core'),
78
- import('@shikijs/engine-javascript'),
79
- import('@shikijs/vscode-textmate'),
80
- import('@mirascript/textmate'),
81
- ]);
82
- this.INITIAL = INITIAL;
83
- return await createHighlighterCore({
84
- langs: [mirascript, mirascriptDoc, mirascriptTemplate],
85
- themes: [],
86
- engine: createJavaScriptRegexEngine(),
87
- });
88
- }
89
-
90
- /**
91
- * Get the shared highlighter instance.
92
- */
93
- private async getHighlighter(): Promise<HighlighterCore> {
94
- this.highlighterPromise ??= this.loadHighlighter();
95
- return this.highlighterPromise;
96
- }
97
-
98
- /** Get tokens provider factory of language */
99
- getTokensProviderFactory(languageId: string): languages.TokensProviderFactory {
100
- return {
101
- create: async () => {
102
- const highlighter = await this.getHighlighter();
103
- const grammar = highlighter.getLanguage(languageId);
104
- return new TokensProvider(grammar, this.INITIAL);
105
- },
106
- };
107
- }
108
- /** @inheritdoc */
109
- dispose(): void {
110
- const promise = this.highlighterPromise;
111
- this.highlighterPromise = null;
112
- if (promise) {
113
- promise
114
- .then((highlighter) => highlighter.dispose())
115
- .catch(() => {
116
- // eslint-disable-next-line no-console
117
- console.error('Failed to dispose highlighter');
118
- });
119
- }
120
- }
121
- }
122
-
123
11
  /** A Monaco tokens provider that uses TextMate grammars. */
124
12
  class TokensProvider implements languages.TokensProvider {
125
- constructor(
126
- private readonly grammar: Grammar,
127
- private readonly INITIAL: StateStack,
128
- ) {}
13
+ constructor(private readonly grammar: Grammar) {}
129
14
  /** @inheritdoc */
130
- getInitialState(): StateStack {
131
- return this.INITIAL;
132
- }
15
+ getInitialState = getInitialState;
133
16
  /** @inheritdoc */
134
17
  tokenize(line: string, state: StateStack): languages.ILineTokens {
135
18
  if (line.length >= TOKENIZE_MAX_LINE_LENGTH) {
@@ -152,7 +35,7 @@ class TokensProvider implements languages.TokensProvider {
152
35
  endState: result.ruleStack,
153
36
  tokens: result.tokens.map((token) => ({
154
37
  startIndex: token.startIndex,
155
- scopes: tokenScope(token.scopes),
38
+ scopes: textmateScopesToMonaco(token.scopes),
156
39
  })),
157
40
  };
158
41
  }
@@ -160,10 +43,18 @@ class TokensProvider implements languages.TokensProvider {
160
43
 
161
44
  /** Register TextMate-backed token providers without changing Monaco themes. */
162
45
  export function registerMiraScriptTokensProvider(): IDisposable[] {
163
- const manager = new HighlighterManager();
164
- const disposables = CONTRIBUTE_IDS.map((id) =>
165
- languages.registerTokensProviderFactory(id, manager.getTokensProviderFactory(id)),
46
+ const disposables = [
47
+ CONTRIBUTE_IDS.mirascript,
48
+ CONTRIBUTE_IDS.mirascriptDoc,
49
+ CONTRIBUTE_IDS.mirascriptTemplate,
50
+ ].map((id) =>
51
+ languages.registerTokensProviderFactory(id, {
52
+ create: async () => {
53
+ const highlighter = await getHighlighter();
54
+ const grammar = highlighter.getLanguage(id);
55
+ return new TokensProvider(grammar);
56
+ },
57
+ }),
166
58
  );
167
- disposables.push(manager);
168
59
  return disposables;
169
60
  }
package/src/contribute.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { mirascriptLanguage, mirascriptDocLanguage, mirascriptTemplateLanguage } from '@mirascript/textmate/language';
2
2
  import { languages } from './monaco-api.js';
3
3
 
4
- export const CONTRIBUTE_IDS = [mirascriptLanguage.name, mirascriptTemplateLanguage.name, mirascriptDocLanguage.name];
4
+ export const CONTRIBUTE_IDS = Object.freeze({
5
+ mirascript: mirascriptLanguage.name,
6
+ mirascriptTemplate: mirascriptTemplateLanguage.name,
7
+ mirascriptDoc: mirascriptDocLanguage.name,
8
+ });
5
9
 
6
10
  /** 注册语言 */
7
11
  export function registerContribution(): void {
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { VmContext } from '@mirascript/mirascript';
2
2
  import { type editor, languages, registerMonacoApi, type IDisposable, type MonacoApi } from './monaco-api.js';
3
- import { registerContribution } from './contribute.js';
3
+ import { CONTRIBUTE_IDS, registerContribution } from './contribute.js';
4
4
  import type { LspFeaturesConfig } from './lsp/index.js';
5
5
  export type { monaco as monacoApi } from './monaco-api.js';
6
6
 
@@ -15,13 +15,13 @@ export class MiraScriptMonacoLoader implements IDisposable {
15
15
  const _loadBasicFeatures = () => void this.loadBasicFeatures();
16
16
  const _loadFullFeatures = () => void this.loadLSPFeatures();
17
17
 
18
- languages.onLanguageEncountered('mirascript', _loadBasicFeatures);
19
- languages.onLanguage('mirascript', _loadFullFeatures);
18
+ languages.onLanguageEncountered(CONTRIBUTE_IDS.mirascript, _loadBasicFeatures);
19
+ languages.onLanguage(CONTRIBUTE_IDS.mirascript, _loadFullFeatures);
20
20
 
21
- languages.onLanguageEncountered('mirascript-template', _loadBasicFeatures);
22
- languages.onLanguage('mirascript-template', _loadFullFeatures);
21
+ languages.onLanguageEncountered(CONTRIBUTE_IDS.mirascriptTemplate, _loadBasicFeatures);
22
+ languages.onLanguage(CONTRIBUTE_IDS.mirascriptTemplate, _loadFullFeatures);
23
23
 
24
- languages.onLanguageEncountered('mirascript-doc', _loadBasicFeatures);
24
+ languages.onLanguageEncountered(CONTRIBUTE_IDS.mirascriptDoc, _loadBasicFeatures);
25
25
  }
26
26
  features: LspFeaturesConfig = {};
27
27
  private _basicFeaturesLoaded = false;
package/src/lsp/index.ts CHANGED
@@ -6,7 +6,7 @@ import { Provider } from './providers/base.js';
6
6
  import { CodeActionProvider } from './providers/code-action-provider.js';
7
7
  import { CodeLensProvider } from './providers/code-lens-provider.js';
8
8
  import { ColorProvider } from './providers/color-provider.js';
9
- import { CompletionItemProvider } from './providers/completion-item-provider.js';
9
+ import { CompletionItemProvider } from './providers/completion-item-provider/index.js';
10
10
  import { DefinitionReferenceProvider } from './providers/definition-reference-provider.js';
11
11
  import { DocumentHighlightProvider } from './providers/document-highlight-provider.js';
12
12
  import { DocumentSymbolProvider } from './providers/document-symbol-provider.js';
@@ -0,0 +1,155 @@
1
+ import { KEYWORDS as HELP_KEYWORDS } from '@mirascript/help';
2
+ import { KEYWORDS, RESERVED_KEYWORDS } from '../../../constants.js';
3
+ import { languages } from '../../../monaco-api.js';
4
+ import { codeblock } from '../../utils.js';
5
+
6
+ /** 构造关键字选项 */
7
+ export function kwSuggestion(kw: string, range: languages.CompletionItemRanges): languages.CompletionItem {
8
+ const doc = (HELP_KEYWORDS as Record<string, string | undefined>)[kw];
9
+ return {
10
+ label: kw,
11
+ kind: languages.CompletionItemKind.Keyword,
12
+ insertText: kw,
13
+ documentation: doc ? { value: doc } : undefined,
14
+ range,
15
+ };
16
+ }
17
+
18
+ const SUGGEST_KEYWORDS: string[] = [];
19
+
20
+ const loadSuggestKeywords = () => {
21
+ if (SUGGEST_KEYWORDS.length > 0) return SUGGEST_KEYWORDS; // 已加载过
22
+ for (const kw of KEYWORDS) {
23
+ if (RESERVED_KEYWORDS.includes(kw as never)) continue; // 跳过保留关键字
24
+ SUGGEST_KEYWORDS.push(kw);
25
+ }
26
+ return SUGGEST_KEYWORDS;
27
+ };
28
+
29
+ export const COMMON_GLOBAL_SUGGESTIONS = (
30
+ range: languages.CompletionItemRanges,
31
+ extension: boolean,
32
+ ): languages.CompletionItem[] => {
33
+ const suggestions: languages.CompletionItem[] = [
34
+ {
35
+ label: 'type',
36
+ kind: languages.CompletionItemKind.Keyword,
37
+ insertText: 'type',
38
+ commitCharacters: ['('],
39
+ documentation: { value: HELP_KEYWORDS.type },
40
+ range,
41
+ },
42
+ {
43
+ label: 'global',
44
+ kind: languages.CompletionItemKind.Keyword,
45
+ insertText: 'global',
46
+ commitCharacters: ['.', '['],
47
+ documentation: { value: HELP_KEYWORDS.global },
48
+ range,
49
+ },
50
+ ];
51
+ if (!extension) {
52
+ suggestions.push(
53
+ {
54
+ label: { label: 'if', description: 'If 表达式' },
55
+ kind: languages.CompletionItemKind.Snippet,
56
+ insertText: 'if ${1:condition} {\n\t$0\n}',
57
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
58
+ documentation: {
59
+ value: `使用 \`if\` 表达式进行条件判断。${codeblock('if condition {\n\t// code\n}')}`,
60
+ },
61
+ range,
62
+ },
63
+ {
64
+ label: { label: 'ifelse', description: 'If-Else 表达式' },
65
+ kind: languages.CompletionItemKind.Snippet,
66
+ insertText: 'if ${1:condition} {\n\t$0\n} else {\n\t\n}',
67
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
68
+ documentation: {
69
+ value: `使用 \`ifelse\` 表达式进行条件判断。${codeblock('if condition {\n\t// code\n} else {\n\t// code\n}')}`,
70
+ },
71
+ range,
72
+ },
73
+ {
74
+ label: { label: 'match', description: 'Match 表达式' },
75
+ kind: languages.CompletionItemKind.Snippet,
76
+ insertText:
77
+ '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}',
78
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
79
+ documentation: {
80
+ 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}')}`,
81
+ },
82
+ range,
83
+ },
84
+ {
85
+ label: { label: 'loop', description: 'Loop 表达式' },
86
+ kind: languages.CompletionItemKind.Snippet,
87
+ insertText: 'loop {\n\t$0\n}',
88
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
89
+ documentation: {
90
+ value: `使用 \`loop\` 表达式进行无限循环。${codeblock('loop {\n\t// code\n}')}`,
91
+ },
92
+ range,
93
+ },
94
+ {
95
+ label: { label: 'while', description: 'While 表达式' },
96
+ kind: languages.CompletionItemKind.Snippet,
97
+ insertText: 'while ${1:condition} {\n\t$0\n}',
98
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
99
+ documentation: {
100
+ value: `使用 \`while\` 表达式进行条件循环。${codeblock('while condition {\n\t// code\n}')}`,
101
+ },
102
+ range,
103
+ },
104
+ {
105
+ label: { label: 'whileelse', description: 'While-Else 表达式' },
106
+ kind: languages.CompletionItemKind.Snippet,
107
+ insertText: 'while ${1:condition} {\n\t$0\n} else {\n\t\n}',
108
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
109
+ documentation: {
110
+ value: `使用 \`whileelse\` 表达式进行条件循环。${codeblock('while condition {\n\t// code\n} else {\n\t// code\n}')}`,
111
+ },
112
+ range,
113
+ },
114
+ {
115
+ label: { label: 'for', description: 'For 表达式' },
116
+ kind: languages.CompletionItemKind.Snippet,
117
+ insertText: 'for ${1:item} in ${2:collection} {\n\t$0\n}',
118
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
119
+ documentation: {
120
+ value: `使用 \`for\` 表达式进行迭代循环。${codeblock('for item in collection {\n\t// code\n}')}`,
121
+ },
122
+ range,
123
+ },
124
+ {
125
+ label: { label: 'forelse', description: 'For-Else 表达式' },
126
+ kind: languages.CompletionItemKind.Snippet,
127
+ insertText: 'for ${1:item} in ${2:collection} {\n\t$0\n} else {\n\t\n}',
128
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
129
+ documentation: {
130
+ value: `使用 \`forelse\` 表达式进行迭代循环。${codeblock('for item in collection {\n\t// code\n} else {\n\t// code\n}')}`,
131
+ },
132
+ range,
133
+ },
134
+ {
135
+ label: { label: 'fn', description: 'Fn 语句' },
136
+ kind: languages.CompletionItemKind.Snippet,
137
+ insertText: 'fn ${1:name}(${2:params}) {\n\t$0\n}',
138
+ insertTextRules: languages.CompletionItemInsertTextRule.InsertAsSnippet,
139
+ documentation: {
140
+ value: `使用 \`fn\` 语句进行函数声明。${codeblock('fn name(params) {\n\t// code\n}')}`,
141
+ },
142
+ range,
143
+ },
144
+ );
145
+
146
+ for (const kw of loadSuggestKeywords()) {
147
+ const exist = suggestions.some(
148
+ (item) => item.label === kw && item.kind === languages.CompletionItemKind.Keyword,
149
+ );
150
+ if (exist) continue;
151
+ suggestions.push(kwSuggestion(kw, range));
152
+ }
153
+ }
154
+ return suggestions;
155
+ };
@@ -0,0 +1,64 @@
1
+ import { isVmWrapper, type VmAny } from '@mirascript/mirascript';
2
+ import { lib } from '@mirascript/mirascript/subtle';
3
+ import { getDeep, getField } from '../../utils.js';
4
+ import type { editor, languages, Position } from '../../../monaco-api.js';
5
+ import { REG_IDENTIFIER_FULL, REG_ORDINAL_FULL } from '../../../constants.js';
6
+ import { DESC_FIELD, type CustomCompletionItem } from './interface.js';
7
+ import { Provider } from '../base.js';
8
+ import { completion } from './utils.js';
9
+
10
+ /** 列出属性 */
11
+ function listFields(obj: VmAny, includeNonEnumerable: boolean): Array<string | number> {
12
+ if (obj == null || typeof obj != 'object') return [];
13
+ if (isVmWrapper(obj)) {
14
+ try {
15
+ return obj.keys(includeNonEnumerable);
16
+ } catch {
17
+ return [];
18
+ }
19
+ }
20
+ return lib.keys(obj);
21
+ }
22
+
23
+ /** 查找变量字段 */
24
+ export async function completeFields(
25
+ model: editor.ITextModel,
26
+ position: Position,
27
+ char: string | undefined,
28
+ range: languages.CompletionItemRanges,
29
+ ): Promise<CustomCompletionItem[]> {
30
+ const compiled = await Provider.getCompileResult(model);
31
+ if (!compiled) return [];
32
+ const access = compiled.fieldAccessAt(model, position);
33
+ if (!access || access.fields.length === 0) return [];
34
+ const { def, fields } = access;
35
+ if ('definition' in def.def) {
36
+ // TODO: suggests local item fields
37
+ return [];
38
+ }
39
+ const vmGlobal = await Provider.getContext(model);
40
+ fields.pop(); // 移除最后一个部分,因为它是当前输入位置的字段名
41
+ const [, value] = getDeep(vmGlobal, def.def.name, fields);
42
+ if (value == null || typeof value != 'object') {
43
+ return [];
44
+ }
45
+ const keys = listFields(value, true);
46
+ const result: CustomCompletionItem[] = [];
47
+ for (const k of keys) {
48
+ const key = String(k);
49
+ if (char && !String(key).toLowerCase().includes(char)) {
50
+ continue;
51
+ }
52
+ if (!REG_IDENTIFIER_FULL.test(key) && !REG_ORDINAL_FULL.test(key)) {
53
+ continue;
54
+ }
55
+ const field = getField(value, key);
56
+ result.push({
57
+ insertText: key,
58
+ range,
59
+ vmParent: isVmWrapper(value) ? value : undefined,
60
+ ...completion(model, DESC_FIELD, key, field, undefined, true),
61
+ });
62
+ }
63
+ return result;
64
+ }
@@ -0,0 +1,82 @@
1
+ import { isVmModule, serialize } from '@mirascript/mirascript';
2
+ import type { editor, languages } from '../../../monaco-api.js';
3
+ import { Provider } from '../base.js';
4
+ import { isDeprecatedGlobal } from '../../utils.js';
5
+ import { REG_IDENTIFIER_FULL, isKeyword } from '../../../constants.js';
6
+ import { DESC_GLOBAL, type CustomCompletionItem } from './interface.js';
7
+ import { completion, filterText } from './utils.js';
8
+
9
+ /** 查找全局变量 */
10
+ export async function completeGlobal(
11
+ model: editor.ITextModel,
12
+ char: string | undefined,
13
+ locals: readonly CustomCompletionItem[],
14
+ range: languages.CompletionItemRanges,
15
+ hasGlobalPrefix: boolean,
16
+ ): Promise<CustomCompletionItem[]> {
17
+ const global = await Provider.getContext(model);
18
+ const suggestions: CustomCompletionItem[] = [];
19
+ const localKeys = new Set(locals.map((item) => item.insertText));
20
+ for (const key of new Set(global.keys())) {
21
+ // skip deprecated globals in completion
22
+ if (!global.has(key) || isDeprecatedGlobal(global, key)) continue;
23
+
24
+ const element = global.get(key);
25
+
26
+ let prefix = key;
27
+ const edits: editor.ISingleEditOperation[] = [];
28
+ if (!REG_IDENTIFIER_FULL.test(key)) {
29
+ const kStr = serialize(key);
30
+ if (hasGlobalPrefix) {
31
+ prefix = `[${kStr}]`;
32
+ // 删除多余的 . 前缀
33
+ edits.push({
34
+ range: {
35
+ startLineNumber: range.replace.startLineNumber,
36
+ startColumn: range.replace.startColumn - 1,
37
+ endLineNumber: range.replace.endLineNumber,
38
+ endColumn: range.replace.endColumn,
39
+ },
40
+ text: '',
41
+ });
42
+ } else {
43
+ prefix = `global[${serialize(key)}]`;
44
+ }
45
+ } else if (!hasGlobalPrefix && (localKeys.has(key) || isKeyword(key))) {
46
+ prefix = `global.${key}`;
47
+ }
48
+
49
+ if (isVmModule(element)) {
50
+ for (const f of element.keys()) {
51
+ if (char && !f.toLowerCase().includes(char)) {
52
+ continue;
53
+ }
54
+ const field = element.get(f);
55
+ if (field === undefined) continue;
56
+
57
+ suggestions.push({
58
+ insertText: `${prefix}.${f}`,
59
+ filterText: filterText(f, char),
60
+ range,
61
+ additionalTextEdits: edits,
62
+ vmParent: element,
63
+ ...completion(model, DESC_GLOBAL, `${key}.${f}`, field, undefined, true),
64
+ });
65
+ }
66
+ }
67
+
68
+ if (char && !key.toLowerCase().includes(char)) {
69
+ continue;
70
+ }
71
+
72
+ suggestions.push({
73
+ insertText: prefix,
74
+ filterText: filterText(key, char),
75
+ range,
76
+ additionalTextEdits: edits,
77
+ vmParent: global,
78
+ ...completion(model, DESC_GLOBAL, key, element, undefined, false),
79
+ });
80
+ }
81
+ return suggestions;
82
+ }
@@ -0,0 +1,57 @@
1
+ import { DiagnosticCode } from '@mirascript/mirascript/subtle';
2
+ import { type editor, languages, type IPosition } from '../../../monaco-api.js';
3
+ import { DESC_LOCAL, type CustomCompletionItem } from './interface.js';
4
+ import { completion, filterText } from './utils.js';
5
+ import { Provider } from '../base.js';
6
+ import type { LocalDefinition } from '../../compile-result.js';
7
+
8
+ /** 创建局部变量 */
9
+ function createLocalCompletionItem(
10
+ model: editor.ITextModel,
11
+ char: string | undefined,
12
+ range: languages.CompletionItemRanges,
13
+ locals: Set<string>,
14
+ { definition, fn }: LocalDefinition,
15
+ ): CustomCompletionItem | null {
16
+ const name = definition.code === DiagnosticCode.ParameterIt ? 'it' : model.getValueInRange(definition.range);
17
+ if (char && !name.toLowerCase().includes(char)) return null;
18
+ if (locals.has(name)) return null; // 子作用域可能会覆盖父作用域的变量
19
+
20
+ locals.add(name);
21
+ const suggestion = {
22
+ insertText: name,
23
+ filterText: filterText(name, char),
24
+ range,
25
+ ...completion(model, DESC_LOCAL, name, undefined, fn, false),
26
+ };
27
+ if (definition.code === DiagnosticCode.LocalModule) {
28
+ suggestion.kind = languages.CompletionItemKind.Module;
29
+ } else if (definition.code === DiagnosticCode.LocalFunction) {
30
+ suggestion.kind = languages.CompletionItemKind.Function;
31
+ }
32
+ return suggestion;
33
+ }
34
+
35
+ /** 查找局部变量 */
36
+ export async function completeLocal(
37
+ model: editor.ITextModel,
38
+ position: IPosition,
39
+ char: string | undefined,
40
+ range: languages.CompletionItemRanges,
41
+ ): Promise<CustomCompletionItem[]> {
42
+ const compiled = await Provider.getCompileResult(model);
43
+ if (!compiled) return [];
44
+ const suggestions: CustomCompletionItem[] = [];
45
+
46
+ let scope = compiled.scopeAt(model, position);
47
+ const locals = new Set<string>();
48
+ while (scope) {
49
+ for (const def of scope.locals) {
50
+ const suggestion = createLocalCompletionItem(model, char, range, locals, def);
51
+ if (suggestion) suggestions.push(suggestion);
52
+ }
53
+ if (!scope.parent) break;
54
+ scope = scope.parent;
55
+ }
56
+ return suggestions;
57
+ }