@mirascript/monaco 0.1.39 → 0.1.41

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.
@@ -10,6 +10,7 @@ import {
10
10
  serialize,
11
11
  } from '@mirascript/mirascript';
12
12
  import { DiagnosticCode } from '@mirascript/mirascript/subtle';
13
+ import { KEYWORDS as HELP_KEYWORDS } from '@mirascript/help';
13
14
  import {
14
15
  type editor,
15
16
  languages,
@@ -20,17 +21,8 @@ import {
20
21
  Range,
21
22
  } from '../../monaco-api.js';
22
23
  import { Provider, type MonacoContext } from './base.js';
23
- import {
24
- codeblock,
25
- getDeep,
26
- valueDoc,
27
- paramsList,
28
- strictContainsPosition,
29
- wordAt,
30
- getField,
31
- listFields,
32
- isDeprecatedGlobal,
33
- } from '../utils.js';
24
+ import { strictContainsPosition, wordAt } from '../monaco-utils.js';
25
+ import { codeblock, getDeep, valueDoc, paramsList, getField, listFields, isDeprecatedGlobal } from '../utils.js';
34
26
  import { KEYWORDS, RESERVED_KEYWORDS, REG_IDENTIFIER_FULL, REG_ORDINAL_FULL, isKeyword } from '../../constants.js';
35
27
  import type { LocalDefinition } from '../compile-result.js';
36
28
 
@@ -59,9 +51,7 @@ const COMMON_GLOBAL_SUGGESTIONS = (
59
51
  kind: languages.CompletionItemKind.Keyword,
60
52
  insertText: 'type',
61
53
  commitCharacters: ['('],
62
- documentation: {
63
- value: `使用 \`type()\` 调用获取表达式的类型。${codeblock('type(expression);\nexpression::type();')}`,
64
- },
54
+ documentation: { value: HELP_KEYWORDS.type },
65
55
  range,
66
56
  },
67
57
  {
@@ -69,9 +59,7 @@ const COMMON_GLOBAL_SUGGESTIONS = (
69
59
  kind: languages.CompletionItemKind.Keyword,
70
60
  insertText: 'global',
71
61
  commitCharacters: ['.', '['],
72
- documentation: {
73
- value: `使用 \`global\` 获取全局变量。${codeblock('global.variableName;\nglobal["variableName"];\n"variableName" in global;')}`,
74
- },
62
+ documentation: { value: HELP_KEYWORDS.global },
75
63
  range,
76
64
  },
77
65
  ];
@@ -183,10 +171,12 @@ const COMMON_GLOBAL_SUGGESTIONS = (
183
171
 
184
172
  /** 构造关键字选项 */
185
173
  function kwSuggestion(kw: string, range: languages.CompletionItemRanges): languages.CompletionItem {
174
+ const doc = (HELP_KEYWORDS as Record<string, string | undefined>)[kw];
186
175
  return {
187
176
  label: kw,
188
177
  kind: languages.CompletionItemKind.Keyword,
189
178
  insertText: kw,
179
+ documentation: doc ? { value: doc } : undefined,
190
180
  range,
191
181
  };
192
182
  }
@@ -1,8 +1,48 @@
1
- import type { CancellationToken, editor, IMarkdownString, IRange, languages, Position } from '../../monaco-api.js';
2
- import { Provider } from './base.js';
3
1
  import { DiagnosticCode } from '@mirascript/constants';
4
- import { codeblock, getDeep, valueDoc, paramsList } from '../utils.js';
2
+ import { convert } from '@mirascript/mirascript/subtle';
3
+ import { KEYWORDS as HELP_KEYWORDS, OPERATORS as HELP_OPERATORS } from '@mirascript/help';
4
+ import { REG_BIN, REG_HEX, REG_NUMBER, REG_OCT } from '../../constants.js';
5
+ import type { editor, CancellationToken, IMarkdownString, IRange, languages, Position } from '../../monaco-api.js';
6
+ import { codeblock, getDeep, valueDoc, paramsList, serializeNumber, serializeInteger } from '../utils.js';
7
+ import { tokenAt } from '../monaco-private.js';
8
+ import { rangeAt } from '../monaco-utils.js';
5
9
  import type { FieldsAccessAt, VariableAccessAt } from '../compile-result.js';
10
+ import { Provider } from './base.js';
11
+
12
+ const OPERATOR_TOKENS_DESC = Object.keys(HELP_OPERATORS as Record<string, string>).sort((a, b) => b.length - a.length);
13
+ const REG_NUMBER_ALL_FULL = new RegExp(
14
+ `^(?:${REG_BIN.source}|${REG_OCT.source}|${REG_HEX.source}|${REG_NUMBER.source})$`,
15
+ REG_NUMBER.flags,
16
+ );
17
+
18
+ const BIN_MAX = 2 ** 32 - 1;
19
+ const OCT_MAX = 8 ** 18 - 1;
20
+ const HEX_MAX = 16 ** 16 - 1;
21
+
22
+ /** 在指定位置查找操作符 */
23
+ function operatorAt(lineContent: string, column: number): { token: string; range: IRange } | undefined {
24
+ const index = Math.max(0, column - 1);
25
+ for (const token of OPERATOR_TOKENS_DESC) {
26
+ for (let offset = 0; offset < token.length; offset++) {
27
+ const start = index - offset;
28
+ if (start < 0) continue;
29
+ const end = start + token.length;
30
+ if (end > lineContent.length) continue;
31
+ if (lineContent.slice(start, end) !== token) continue;
32
+ if (index < start || index >= end) continue;
33
+ return {
34
+ token,
35
+ range: {
36
+ startLineNumber: 0,
37
+ startColumn: start + 1,
38
+ endLineNumber: 0,
39
+ endColumn: end + 1,
40
+ },
41
+ };
42
+ }
43
+ }
44
+ return undefined;
45
+ }
6
46
 
7
47
  /** @inheritdoc */
8
48
  export class HoverProvider extends Provider implements languages.HoverProvider {
@@ -120,6 +160,79 @@ export class HoverProvider extends Provider implements languages.HoverProvider {
120
160
  range,
121
161
  };
122
162
  }
163
+
164
+ /** 语法元素提示 */
165
+ private provideSyntaxHover(model: editor.ITextModel, position: Position): languages.Hover | undefined {
166
+ const token = tokenAt(model, position);
167
+ if (token?.type && token.type !== 'other') {
168
+ return undefined;
169
+ }
170
+
171
+ if (token?.text) {
172
+ if (token.text in HELP_KEYWORDS) {
173
+ const doc = HELP_KEYWORDS[token.text as keyof typeof HELP_KEYWORDS];
174
+ return {
175
+ contents: [{ value: doc }],
176
+ range: rangeAt(position, token),
177
+ };
178
+ }
179
+
180
+ if (token.text in HELP_OPERATORS) {
181
+ const doc = HELP_OPERATORS[token.text as keyof typeof HELP_OPERATORS];
182
+ return {
183
+ contents: [{ value: doc }],
184
+ range: rangeAt(position, token),
185
+ };
186
+ }
187
+
188
+ if (REG_NUMBER_ALL_FULL.test(token.text)) {
189
+ const num = convert.toNumber(token.text.replaceAll('_', ''), null);
190
+ if (num == null) return undefined;
191
+ const contents: IMarkdownString[] = [
192
+ { value: `数字字面量` },
193
+ { value: codeblock('val: ' + serializeNumber(num)) },
194
+ ];
195
+ if (Number.isInteger(num)) {
196
+ const abs = Math.abs(num);
197
+ if (abs <= BIN_MAX) {
198
+ contents.push({ value: codeblock('bin: ' + serializeInteger(num, 2)) });
199
+ }
200
+ if (abs <= OCT_MAX) {
201
+ contents.push({ value: codeblock('oct: ' + serializeInteger(num, 8)) });
202
+ }
203
+ if (abs <= HEX_MAX) {
204
+ contents.push({ value: codeblock('hex: ' + serializeInteger(num, 16)) });
205
+ }
206
+ }
207
+ return {
208
+ contents,
209
+ range: rangeAt(position, token),
210
+ };
211
+ }
212
+ }
213
+
214
+ const word = model.getWordAtPosition(position);
215
+ if (word?.word && word.word in HELP_KEYWORDS) {
216
+ const doc = HELP_KEYWORDS[word.word as keyof typeof HELP_KEYWORDS];
217
+ return {
218
+ contents: [{ value: doc }],
219
+ range: rangeAt(position, word),
220
+ };
221
+ }
222
+
223
+ const lineContent = model.getLineContent(position.lineNumber);
224
+ const hit = operatorAt(lineContent, position.column);
225
+ if (hit && hit.token in HELP_OPERATORS) {
226
+ const doc = HELP_OPERATORS[hit.token as keyof typeof HELP_OPERATORS];
227
+ return {
228
+ contents: [{ value: doc }],
229
+ range: rangeAt(position, hit.range),
230
+ };
231
+ }
232
+
233
+ return undefined;
234
+ }
235
+
123
236
  /** @inheritdoc */
124
237
  async provideHover(
125
238
  model: editor.ITextModel,
@@ -128,8 +241,9 @@ export class HoverProvider extends Provider implements languages.HoverProvider {
128
241
  context?: languages.HoverContext<languages.Hover>,
129
242
  ): Promise<languages.Hover | undefined> {
130
243
  const value = await this.getValueAt(model, position);
131
- if (!value) return undefined;
132
- if ('fields' in value) {
244
+ if (!value) {
245
+ return this.provideSyntaxHover(model, position);
246
+ } else if ('fields' in value) {
133
247
  return this.provideFieldHover(model, value.range, value.fields);
134
248
  } else {
135
249
  return this.provideVariableHover(model, value.variable);
@@ -1,8 +1,9 @@
1
1
  import { DiagnosticCode } from '@mirascript/constants';
2
+ import { getVmFunctionInfo } from '@mirascript/mirascript';
2
3
  import { type editor, type languages, type CancellationToken, Position, Range } from '../../monaco-api.js';
3
4
  import { Provider } from './base.js';
4
- import { fnSignature, getDeep, localParamSignature, strictContainsPosition } from '../utils.js';
5
- import { getVmFunctionInfo } from '@mirascript/mirascript';
5
+ import { fnSignature, getDeep, localParamSignature } from '../utils.js';
6
+ import { strictContainsPosition } from '../monaco-utils.js';
6
7
 
7
8
  /** @inheritdoc */
8
9
  export class SignatureHelpProvider extends Provider implements languages.SignatureHelpProvider {
package/src/lsp/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DiagnosticCode } from '@mirascript/constants';
2
- import { type editor, Range, type IPosition, type IRange } from '../monaco-api.js';
2
+ import { type editor, Range } from '../monaco-api.js';
3
3
  import {
4
4
  getVmFunctionInfo,
5
5
  isVmArray,
@@ -155,17 +155,76 @@ export function codeblock(value: string): string {
155
155
  return `\n${CODEBLOCK_FENCE}${lang}\n${value}\n${CODEBLOCK_FENCE}\n`;
156
156
  }
157
157
 
158
- /** 检查位置是否在范围内,且范围非空 */
159
- export function strictContainsPosition(range: IRange, position: IPosition): boolean {
160
- return !Range.isEmpty(range) && Range.containsPosition(range, position);
158
+ /** 格式化数字 */
159
+ function serializeIntegerImpl(num: number, base: number, prefix: string, sep: number): string {
160
+ let str = Math.abs(num).toString(base);
161
+ if (base > 10) str = str.toUpperCase();
162
+ const sepSize = Math.abs(sep);
163
+ if (sep !== 0 && str.length > sepSize) {
164
+ const seg = [];
165
+ if (sep > 0) {
166
+ while (str.length > sepSize) {
167
+ seg.unshift(str.slice(-sepSize));
168
+ str = str.slice(0, -sepSize);
169
+ }
170
+ if (str.length > 0) {
171
+ seg.unshift(str);
172
+ }
173
+ } else {
174
+ while (str.length > sepSize) {
175
+ seg.push(str.slice(0, sepSize));
176
+ str = str.slice(sepSize);
177
+ }
178
+ if (str.length > 0) {
179
+ seg.push(str);
180
+ }
181
+ }
182
+ str = seg.join('_');
183
+ }
184
+ return (num < 0 ? '-' : '') + prefix + str;
161
185
  }
162
186
 
163
- /** 获取单词 */
164
- export function wordAt(model: editor.ITextModel, position: IPosition): { word: string; range: Range } | undefined {
165
- const word = model.getWordAtPosition(position);
166
- if (!word) return undefined;
167
- const range = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
168
- return { word: word.word, range };
187
+ /** 格式化数字 */
188
+ export function serializeInteger(num: number, base: 2 | 8 | 16, sep = true): string {
189
+ const prefix = base === 2 ? '0b' : base === 8 ? '0o' : '0x';
190
+ const sepSize = sep ? (base === 2 ? 8 : base === 8 ? 6 : 4) : 0;
191
+ return serializeIntegerImpl(num, base, prefix, sepSize);
192
+ }
193
+
194
+ /** 格式化数字 */
195
+ export function serializeNumber(num: number): string {
196
+ if (!Number.isFinite(num)) {
197
+ return serialize(num);
198
+ }
199
+ const str = String(num);
200
+ const dot = str.indexOf('.');
201
+ const exp = str.indexOf('e');
202
+ let intPart: string;
203
+ let fracPart: string;
204
+ let expPart: string;
205
+ if (dot >= 0) {
206
+ intPart = str.slice(0, dot);
207
+ if (exp >= 0) {
208
+ fracPart = str.slice(dot + 1, exp);
209
+ expPart = str.slice(exp);
210
+ } else {
211
+ fracPart = str.slice(dot + 1);
212
+ expPart = '';
213
+ }
214
+ } else {
215
+ if (exp >= 0) {
216
+ intPart = str.slice(0, exp);
217
+ fracPart = '';
218
+ expPart = str.slice(exp);
219
+ } else {
220
+ intPart = str;
221
+ fracPart = '';
222
+ expPart = '';
223
+ }
224
+ }
225
+ if (intPart.length > 5) intPart = serializeIntegerImpl(Number(intPart), 10, '', 3);
226
+ if (fracPart.length > 5) fracPart = serializeIntegerImpl(Number(fracPart), 10, '', -3);
227
+ return intPart + (fracPart ? '.' + fracPart : '') + expPart;
169
228
  }
170
229
 
171
230
  /** 将值序列化为便于展示的字符串 */
@@ -177,6 +236,9 @@ function serializeForDisplayInner(value: VmValue, maxWidth: number): string {
177
236
  }
178
237
  return `${serializeString(value.slice(0, maxWidth))}..`;
179
238
  }
239
+ if (typeof value === 'number') {
240
+ return serializeNumber(value);
241
+ }
180
242
  if (isVmPrimitive(value)) {
181
243
  return serialize(value);
182
244
  }