@fc-components/monaco-editor 0.1.13 → 0.1.14

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.
@@ -0,0 +1,4 @@
1
+ import type * as monacoTypes from 'monaco-editor/esm/vs/editor/editor.api';
2
+ import { YamlEditorSchema } from '../types';
3
+ export declare type Monaco = typeof monacoTypes;
4
+ export declare function getYamlCompletionProvider(monaco: Monaco, schemas?: YamlEditorSchema[]): monacoTypes.languages.CompletionItemProvider;
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import type * as monacoTypes from 'monaco-editor/esm/vs/editor/editor.api';
3
+ import type { YamlEditorSchema } from './types';
4
+ interface YamlEditorProps {
5
+ size?: 'small' | 'middle' | 'large';
6
+ theme?: 'light' | 'dark';
7
+ value?: string;
8
+ placeholder?: string;
9
+ enableAutocomplete?: boolean;
10
+ readOnly?: boolean;
11
+ disabled?: boolean;
12
+ schemas?: YamlEditorSchema[];
13
+ onChange?: (value: string) => void;
14
+ onEnter?: (value: string) => void;
15
+ onBlur?: (value: string) => void;
16
+ editorDidMount?: (editor: monacoTypes.editor.IStandaloneCodeEditor) => void;
17
+ }
18
+ export default function YamlEditor(props: YamlEditorProps): React.JSX.Element;
19
+ export {};
@@ -0,0 +1,39 @@
1
+ export interface YamlCompletionItem {
2
+ label: string;
3
+ insertText: string;
4
+ detail?: string;
5
+ documentation?: string;
6
+ type: YamlCompletionType;
7
+ triggerOnInsert?: boolean;
8
+ }
9
+ export declare type YamlCompletionType = 'KEYWORD' | 'VALUE' | 'KEY' | 'BOOLEAN' | 'NUMBER' | 'STRING';
10
+ export interface YamlSchema {
11
+ properties?: Record<string, YamlSchemaProperty>;
12
+ required?: string[];
13
+ type?: string;
14
+ enum?: string[];
15
+ items?: YamlSchema;
16
+ }
17
+ export interface YamlSchemaProperty {
18
+ type?: string | string[];
19
+ description?: string;
20
+ enum?: string[];
21
+ enumDescriptions?: string[];
22
+ properties?: Record<string, YamlSchemaProperty>;
23
+ items?: YamlSchema;
24
+ required?: string[];
25
+ default?: any;
26
+ }
27
+ export interface YamlEditorSchema {
28
+ schema: YamlSchema;
29
+ name?: string;
30
+ description?: string;
31
+ }
32
+ export interface YamlValidationError {
33
+ message: string;
34
+ startLineNumber: number;
35
+ endLineNumber: number;
36
+ startColumn: number;
37
+ endColumn: number;
38
+ severity?: 'error' | 'warning' | 'info';
39
+ }
@@ -0,0 +1,3 @@
1
+ import { YamlValidationError, YamlEditorSchema } from './types';
2
+ export declare function validateYaml(content: string, schemas?: YamlEditorSchema[]): YamlValidationError[];
3
+ export declare function parseYamlValue(value: string): any;
@@ -0,0 +1,56 @@
1
+ export declare const languageConfiguration: {
2
+ wordPattern: RegExp;
3
+ comments: {
4
+ lineComment: string;
5
+ };
6
+ brackets: string[][];
7
+ autoClosingPairs: {
8
+ open: string;
9
+ close: string;
10
+ }[];
11
+ surroundingPairs: {
12
+ open: string;
13
+ close: string;
14
+ }[];
15
+ folding: {
16
+ offSide: boolean;
17
+ };
18
+ };
19
+ export declare const language: {
20
+ tokenPostfix: string;
21
+ brackets: {
22
+ token: string;
23
+ open: string;
24
+ close: string;
25
+ }[];
26
+ keywords: string[];
27
+ numberInteger: RegExp;
28
+ numberFloat: RegExp;
29
+ numberOctal: RegExp;
30
+ numberHex: RegExp;
31
+ numberInfinity: RegExp;
32
+ numberNaN: RegExp;
33
+ numberDate: RegExp;
34
+ escapes: RegExp;
35
+ tokenizer: {
36
+ root: ((string | RegExp)[] | {
37
+ include: string;
38
+ } | (RegExp | string[])[] | (RegExp | {
39
+ cases: {
40
+ '@keywords': string;
41
+ '@default': string;
42
+ };
43
+ })[])[];
44
+ whitespace: (string | RegExp)[][];
45
+ comment: (string | RegExp)[][];
46
+ doubleQuotedString: (string | RegExp)[][];
47
+ singleQuotedString: (string | RegExp)[][];
48
+ blockScalar: (string | RegExp)[][];
49
+ flowMap: ((string | RegExp)[] | {
50
+ include: string;
51
+ })[];
52
+ flowSequence: ((string | RegExp)[] | {
53
+ include: string;
54
+ })[];
55
+ };
56
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.13",
6
+ "version": "0.1.14",
7
7
  "license": "MIT",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/monaco-editor.esm.js",
package/src/index.tsx CHANGED
@@ -1,3 +1,5 @@
1
1
  import promql from './promql';
2
+ import yaml from './yaml';
2
3
 
3
4
  export { promql as PromQLMonacoEditor };
5
+ export { yaml as YamlMonacoEditor };
@@ -0,0 +1,431 @@
1
+ import type * as monacoTypes from 'monaco-editor/esm/vs/editor/editor.api';
2
+ import { YamlCompletionItem, YamlCompletionType, YamlEditorSchema } from '../types';
3
+
4
+ export type Monaco = typeof monacoTypes;
5
+
6
+ function getMonacoCompletionItemKind(type: YamlCompletionType, monaco: Monaco): monacoTypes.languages.CompletionItemKind {
7
+ switch (type) {
8
+ case 'KEYWORD':
9
+ return monaco.languages.CompletionItemKind.Keyword;
10
+ case 'VALUE':
11
+ return monaco.languages.CompletionItemKind.Value;
12
+ case 'KEY':
13
+ return monaco.languages.CompletionItemKind.Property;
14
+ case 'BOOLEAN':
15
+ return monaco.languages.CompletionItemKind.Constant;
16
+ case 'NUMBER':
17
+ return monaco.languages.CompletionItemKind.Value;
18
+ case 'STRING':
19
+ return monaco.languages.CompletionItemKind.Text;
20
+ default:
21
+ return monaco.languages.CompletionItemKind.Text;
22
+ }
23
+ }
24
+
25
+ export function getYamlCompletionProvider(monaco: Monaco, schemas: YamlEditorSchema[] = []): monacoTypes.languages.CompletionItemProvider {
26
+ // 更准确的位置分析
27
+ const analyzePosition = (model: monacoTypes.editor.ITextModel, position: monacoTypes.Position) => {
28
+ const line = model.getLineContent(position.lineNumber);
29
+ const column = position.column;
30
+ const beforeCursor = line.substring(0, column - 1);
31
+ const trimmedLine = line.trim();
32
+
33
+ // 检查是否在值位置
34
+ const colonIndex = beforeCursor.indexOf(':');
35
+ const isAfterColon = colonIndex !== -1;
36
+
37
+ // 如果在冒号后面,检查是否是值位置
38
+ let isInValuePosition = false;
39
+ let currentKey = null;
40
+
41
+ if (isAfterColon) {
42
+ let keyPart = beforeCursor.substring(0, colonIndex).trim();
43
+ // 如果键名以 - 开头(数组项),去掉 - 和后面的空格
44
+ if (keyPart.startsWith('- ')) {
45
+ keyPart = keyPart.substring(2).trim();
46
+ } else if (keyPart === '-') {
47
+ // 如果只有一个 -,说明键名在下一个词
48
+ keyPart = '';
49
+ }
50
+ currentKey = keyPart;
51
+ // 检查冒号后面是否有空格或者光标紧跟在冒号后面
52
+ const afterColon = beforeCursor.substring(colonIndex + 1);
53
+ isInValuePosition = afterColon.length === 0 || /^\s+$/.test(afterColon);
54
+ }
55
+
56
+ // 检查是否已经有完整的值
57
+ const hasCompleteValue = trimmedLine.includes(':') && !trimmedLine.endsWith(':') && trimmedLine.split(':')[1].trim().length > 0;
58
+
59
+ return {
60
+ isInKeyPosition: !isAfterColon || trimmedLine.endsWith(':'),
61
+ isInValuePosition: isInValuePosition && !hasCompleteValue,
62
+ isAtEndOfCompletedValue: hasCompleteValue,
63
+ currentKey,
64
+ line: trimmedLine,
65
+ beforeCursor,
66
+ afterCursor: line.substring(column - 1),
67
+ };
68
+ }; // 从 schemas 中提取某个键的枚举值
69
+ const getEnumValuesFromSchemas = (keyName: string, path: string[] = []): YamlCompletionItem[] => {
70
+ const completions: YamlCompletionItem[] = [];
71
+
72
+ for (const schemaItem of schemas) {
73
+ const enumValues = extractEnumValues(schemaItem.schema, keyName, path);
74
+ completions.push(...enumValues);
75
+ }
76
+
77
+ return completions;
78
+ };
79
+
80
+ // 递归提取 schema 中的枚举值
81
+ const extractEnumValues = (schema: any, targetKey: string, currentPath: string[] = []): YamlCompletionItem[] => {
82
+ const completions: YamlCompletionItem[] = [];
83
+
84
+ if (!schema || !schema.properties) {
85
+ return completions;
86
+ }
87
+
88
+ // 如果路径为空,直接在当前层级查找
89
+ if (currentPath.length === 0) {
90
+ const prop = schema.properties[targetKey];
91
+ if (prop && prop.enum) {
92
+ return createEnumCompletions(prop);
93
+ }
94
+ return completions;
95
+ }
96
+
97
+ // 按路径导航到目标位置
98
+ let currentSchema = schema;
99
+ for (const pathSegment of currentPath) {
100
+ if (currentSchema.properties && currentSchema.properties[pathSegment]) {
101
+ currentSchema = currentSchema.properties[pathSegment];
102
+ // 如果是数组类型,使用 items 的 schema
103
+ if (currentSchema.type === 'array' && currentSchema.items) {
104
+ currentSchema = currentSchema.items;
105
+ }
106
+ } else {
107
+ // 路径不存在,返回空
108
+ return completions;
109
+ }
110
+ }
111
+
112
+ // 在目标位置查找枚举值
113
+ if (currentSchema.properties && currentSchema.properties[targetKey]) {
114
+ const prop = currentSchema.properties[targetKey];
115
+ if (prop.enum) {
116
+ return createEnumCompletions(prop);
117
+ }
118
+ }
119
+
120
+ return completions;
121
+ };
122
+
123
+ // 创建枚举值补全项,支持 enumDescriptions 描述
124
+ const createEnumCompletions = (prop: any): YamlCompletionItem[] => {
125
+ const completions: YamlCompletionItem[] = [];
126
+
127
+ if (!prop.enum) {
128
+ return completions;
129
+ }
130
+
131
+ prop.enum.forEach((enumValue: string, index: number) => {
132
+ let description = prop.description || `Enum value: ${enumValue}`;
133
+
134
+ // 如果有 enumDescriptions 数组,使用对应索引的描述
135
+ if (prop.enumDescriptions && prop.enumDescriptions[index]) {
136
+ description = prop.enumDescriptions[index];
137
+ }
138
+
139
+ completions.push({
140
+ label: enumValue,
141
+ insertText: enumValue,
142
+ detail: description,
143
+ documentation: description,
144
+ type: 'VALUE',
145
+ });
146
+ });
147
+
148
+ return completions;
149
+ };
150
+
151
+ // 从 schemas 中提取可用的键名
152
+ const getKeyCompletionsFromSchemas = (path: string[] = []): YamlCompletionItem[] => {
153
+ const completions: YamlCompletionItem[] = [];
154
+ const addedKeys = new Set<string>(); // 避免重复
155
+
156
+ for (const schemaItem of schemas) {
157
+ const keyCompletions = extractKeyCompletions(schemaItem.schema, path);
158
+ for (const completion of keyCompletions) {
159
+ if (!addedKeys.has(completion.label)) {
160
+ addedKeys.add(completion.label);
161
+ completions.push(completion);
162
+ }
163
+ }
164
+ }
165
+
166
+ return completions;
167
+ };
168
+
169
+ // 递归提取 schema 中的键名
170
+ const extractKeyCompletions = (schema: any, targetPath: string[] = []): YamlCompletionItem[] => {
171
+ const completions: YamlCompletionItem[] = [];
172
+ let currentSchema = schema;
173
+
174
+ // 导航到目标路径
175
+ for (const pathSegment of targetPath) {
176
+ if (currentSchema.properties && currentSchema.properties[pathSegment]) {
177
+ currentSchema = currentSchema.properties[pathSegment];
178
+ if (currentSchema.type === 'array' && currentSchema.items) {
179
+ currentSchema = currentSchema.items;
180
+ }
181
+ } else {
182
+ return completions; // 路径不存在
183
+ }
184
+ }
185
+
186
+ // 获取当前层级的属性
187
+ if (currentSchema.properties) {
188
+ for (const [key, property] of Object.entries(currentSchema.properties)) {
189
+ const prop = property as any;
190
+ let insertText = `${key}: `;
191
+ let shouldTriggerOnInsert = false;
192
+
193
+ // 根据属性类型调整插入文本和是否触发补全
194
+ if (prop.type === 'object') {
195
+ insertText = `${key}:\n `;
196
+ shouldTriggerOnInsert = true; // 对象类型需要触发键补全
197
+ } else if (prop.type === 'array') {
198
+ insertText = `${key}:\n - `;
199
+ shouldTriggerOnInsert = true; // 数组类型需要触发补全
200
+ } else if (prop.enum && prop.enum.length > 0) {
201
+ // 有枚举值的字段需要触发值补全
202
+ shouldTriggerOnInsert = true;
203
+ } else if (prop.type === 'boolean') {
204
+ // 布尔类型需要触发 true/false 补全
205
+ shouldTriggerOnInsert = true;
206
+ } else if (prop.type === 'number') {
207
+ // 数字类型需要触发数字补全
208
+ shouldTriggerOnInsert = true;
209
+ }
210
+ // 对于普通字符串字段,不触发补全
211
+
212
+ completions.push({
213
+ label: key,
214
+ insertText,
215
+ detail: prop.description || `Property: ${key}`,
216
+ documentation: prop.description,
217
+ type: 'KEY',
218
+ triggerOnInsert: shouldTriggerOnInsert,
219
+ });
220
+ }
221
+ }
222
+
223
+ return completions;
224
+ };
225
+
226
+ const getFieldType = (keyName: string, path: string[] = []): string | null => {
227
+ for (const schemaItem of schemas) {
228
+ const fieldType = extractFieldType(schemaItem.schema, keyName, path);
229
+ if (fieldType) {
230
+ return fieldType;
231
+ }
232
+ }
233
+ return null;
234
+ };
235
+
236
+ const extractFieldType = (schema: any, targetKey: string, currentPath: string[] = []): string | null => {
237
+ if (!schema || !schema.properties) {
238
+ return null;
239
+ }
240
+
241
+ // 如果路径为空,直接在当前层级查找
242
+ if (currentPath.length === 0) {
243
+ const prop = schema.properties[targetKey];
244
+ return prop ? prop.type : null;
245
+ }
246
+
247
+ // 按路径导航到目标位置
248
+ let currentSchema = schema;
249
+ for (const pathSegment of currentPath) {
250
+ if (currentSchema.properties && currentSchema.properties[pathSegment]) {
251
+ currentSchema = currentSchema.properties[pathSegment];
252
+ // 如果是数组类型,使用 items 的 schema
253
+ if (currentSchema.type === 'array' && currentSchema.items) {
254
+ currentSchema = currentSchema.items;
255
+ }
256
+ } else {
257
+ // 路径不存在,返回空
258
+ return null;
259
+ }
260
+ }
261
+
262
+ // 在目标位置查找字段类型
263
+ if (currentSchema.properties && currentSchema.properties[targetKey]) {
264
+ const prop = currentSchema.properties[targetKey];
265
+ return prop.type;
266
+ }
267
+
268
+ return null;
269
+ };
270
+
271
+ const getCompletions = (model: monacoTypes.editor.ITextModel, position: monacoTypes.Position): YamlCompletionItem[] => {
272
+ const wordInfo = model.getWordAtPosition(position);
273
+ const word = wordInfo ? wordInfo.word : '';
274
+
275
+ // 使用新的位置分析
276
+ const positionInfo = analyzePosition(model, position);
277
+ const currentPath = getCurrentPath(model, position);
278
+
279
+ const completions: YamlCompletionItem[] = [];
280
+
281
+ // 如果已经有完整的值,不提供任何补全
282
+ if (positionInfo.isAtEndOfCompletedValue) {
283
+ return [];
284
+ }
285
+
286
+ // 如果在值位置,提供值的补全
287
+ if (positionInfo.isInValuePosition && positionInfo.currentKey) {
288
+ // 查找枚举值补全
289
+ const enumCompletions = getEnumValuesFromSchemas(positionInfo.currentKey, currentPath);
290
+
291
+ if (enumCompletions.length > 0) {
292
+ // 如果有枚举值,只返回枚举值
293
+ completions.push(...enumCompletions);
294
+ } else {
295
+ // 如果没有枚举值,检查字段类型
296
+ const fieldType = getFieldType(positionInfo.currentKey, currentPath);
297
+
298
+ if (fieldType === 'boolean') {
299
+ // 只为布尔类型提供 true/false
300
+ completions.push(
301
+ {
302
+ label: 'true',
303
+ insertText: 'true',
304
+ detail: 'Boolean true value',
305
+ type: 'BOOLEAN',
306
+ },
307
+ {
308
+ label: 'false',
309
+ insertText: 'false',
310
+ detail: 'Boolean false value',
311
+ type: 'BOOLEAN',
312
+ },
313
+ );
314
+ } else if (fieldType === 'number') {
315
+ // 为数字类型提供基本示例
316
+ completions.push({
317
+ label: '0',
318
+ insertText: '0',
319
+ detail: 'Number value',
320
+ type: 'NUMBER',
321
+ });
322
+ }
323
+ // 对于 string 类型或其他类型,不提供任何补全
324
+ }
325
+ }
326
+ // 如果在键位置且不在值位置,提供键的补全
327
+ else if (positionInfo.isInKeyPosition && !positionInfo.isInValuePosition) {
328
+ // 从 schemas 中获取键补全
329
+ const schemaKeyCompletions = getKeyCompletionsFromSchemas(currentPath);
330
+ completions.push(...schemaKeyCompletions);
331
+ }
332
+
333
+ // 过滤匹配的项目
334
+ const filtered = completions.filter((item) => !word || item.label.toLowerCase().includes(word.toLowerCase()));
335
+
336
+ return filtered;
337
+ };
338
+
339
+ const getCurrentPath = (model: monacoTypes.editor.ITextModel, position: monacoTypes.Position): string[] => {
340
+ const path: string[] = [];
341
+ const lines = model.getLinesContent();
342
+ const currentLineNumber = position.lineNumber - 1; // Monaco uses 1-based line numbers
343
+
344
+ if (currentLineNumber >= lines.length) {
345
+ return path;
346
+ }
347
+
348
+ const currentLine = lines[currentLineNumber];
349
+ const currentIndent = getIndentLevel(currentLine);
350
+
351
+ // 从当前行开始向上查找父级键
352
+ let targetIndent = currentIndent;
353
+
354
+ for (let i = currentLineNumber; i >= 0; i--) {
355
+ const line = lines[i];
356
+ const lineIndent = getIndentLevel(line);
357
+ const trimmed = line.trim();
358
+
359
+ // 跳过空行和注释
360
+ if (!trimmed || trimmed.startsWith('#')) {
361
+ continue;
362
+ }
363
+
364
+ // 如果缩进小于目标缩进
365
+ if (lineIndent < targetIndent) {
366
+ // 检查是否是数组项(以 - 开头)
367
+ if (trimmed.startsWith('-')) {
368
+ // 数组项不添加到路径中,只更新缩进继续查找
369
+ targetIndent = lineIndent;
370
+ continue;
371
+ }
372
+
373
+ // 如果包含冒号,这是一个父级键
374
+ if (trimmed.includes(':')) {
375
+ const key = trimmed.split(':')[0].trim();
376
+ if (key) {
377
+ path.unshift(key);
378
+ targetIndent = lineIndent; // 更新目标缩进,继续向上查找
379
+ }
380
+ }
381
+ }
382
+ }
383
+
384
+ return path;
385
+ };
386
+
387
+ const getIndentLevel = (line: string): number => {
388
+ return line.length - line.trimLeft().length;
389
+ };
390
+
391
+ const provideCompletionItems = (
392
+ model: monacoTypes.editor.ITextModel,
393
+ position: monacoTypes.Position,
394
+ ): monacoTypes.languages.ProviderResult<monacoTypes.languages.CompletionList> => {
395
+ const word = model.getWordAtPosition(position);
396
+ const range =
397
+ word != null
398
+ ? monaco.Range.lift({
399
+ startLineNumber: position.lineNumber,
400
+ endLineNumber: position.lineNumber,
401
+ startColumn: word.startColumn,
402
+ endColumn: word.endColumn,
403
+ })
404
+ : monaco.Range.fromPositions(position);
405
+
406
+ const items = getCompletions(model, position);
407
+
408
+ const suggestions: monacoTypes.languages.CompletionItem[] = items.map((item, index) => ({
409
+ kind: getMonacoCompletionItemKind(item.type, monaco),
410
+ label: item.label,
411
+ insertText: item.insertText,
412
+ detail: item.detail,
413
+ documentation: item.documentation,
414
+ sortText: index.toString().padStart(3, '0'),
415
+ range,
416
+ command: item.triggerOnInsert
417
+ ? {
418
+ id: 'editor.action.triggerSuggest',
419
+ title: '',
420
+ }
421
+ : undefined,
422
+ }));
423
+
424
+ return { suggestions };
425
+ };
426
+
427
+ return {
428
+ triggerCharacters: [':', ' ', '-', '\n', '\t'],
429
+ provideCompletionItems,
430
+ };
431
+ }