@tntd/monaco-editor 1.0.1 → 1.0.3

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,157 @@
1
+ export class SuggestionPlugin {
2
+ constructor(options = {}) {
3
+ this.name = 'suggestion';
4
+ this.options = options;
5
+ this.disposable = null;
6
+ this.suggestTime = null;
7
+ this.baseEditor = null;
8
+ this.methodInfo = null;
9
+ }
10
+
11
+ async apply(baseEditor) {
12
+ this.baseEditor = baseEditor;
13
+ const { monaco } = baseEditor;
14
+ const { language, triggerCharacters: originTriggerCharacters, registerSuggestions, defaultLanguages } = this.options;
15
+ this.dispose();
16
+
17
+ let triggerCharacters = originTriggerCharacters;
18
+ // 如果是函数库,默认增加# 输入#默认带出级联
19
+ if (language?.startsWith('formulaLang')) {
20
+ if (triggerCharacters?.length && !triggerCharacters.includes('#')) {
21
+ triggerCharacters.push('#');
22
+ }
23
+ if (!triggerCharacters?.length) {
24
+ triggerCharacters = ['#'];
25
+ }
26
+ }
27
+
28
+ if (!monaco) return;
29
+
30
+ try {
31
+ // 获取语言关键字
32
+ const typescriptKeywords = await monaco.languages
33
+ .getLanguages()
34
+ .find((lang) => lang.id === language)
35
+ ?.loader?.();
36
+
37
+ let keywords = [];
38
+ if (typescriptKeywords?.language?.keywords) {
39
+ keywords = typescriptKeywords.language.keywords;
40
+ }
41
+
42
+ // 注册自动完成提供程序
43
+ this.disposable = monaco.languages.registerCompletionItemProvider(language, {
44
+ ...(triggerCharacters && { triggerCharacters }),
45
+ provideCompletionItems: (model, position) => {
46
+ const { lineNumber, column } = position;
47
+ let suggestions = [];
48
+
49
+ const textBeforePointer = model.getValueInRange({
50
+ startLineNumber: lineNumber,
51
+ startColumn: 0,
52
+ endLineNumber: lineNumber,
53
+ endColumn: column
54
+ });
55
+
56
+ const contents = textBeforePointer.trim().split(/\s+/);
57
+ const lastContents = contents[contents?.length - 1]; // 获取最后一段非空字符串
58
+
59
+ // 添加语言关键字建议
60
+ if (lastContents && !defaultLanguages?.includes(language)) {
61
+ keywords.forEach((key) => {
62
+ suggestions.push({
63
+ detail: 'keyword',
64
+ label: key,
65
+ insertText: key,
66
+ kind: monaco.languages.CompletionItemKind.Keyword
67
+ });
68
+ });
69
+ }
70
+
71
+ if (registerSuggestions && typeof registerSuggestions === 'function') {
72
+ const newSuggestions = registerSuggestions(model, position, suggestions, monaco);
73
+ if (newSuggestions?.suggestions) {
74
+ suggestions = newSuggestions.suggestions;
75
+ }
76
+ }
77
+
78
+ // 如果是函数库 支持
79
+ if (language?.startsWith('formulaLang')) {
80
+ const formulaSuggestions = this.formulaRegisterSuggestions(model, position, suggestions, monaco);
81
+ if (formulaSuggestions?.suggestions) {
82
+ suggestions = formulaSuggestions.suggestions;
83
+ }
84
+ }
85
+
86
+ return { suggestions };
87
+ }
88
+ });
89
+ } catch (error) {
90
+ console.error('Failed to register completion provider:', error);
91
+ }
92
+ }
93
+
94
+ // sql 定义注册
95
+ formulaRegisterSuggestions = (model, position, suggestions = [], monaco) => {
96
+ let methodList = this.options.methodList;
97
+ if (this.methodInfo) {
98
+ methodList = methodList?.filter((item) => item?.name?.startsWith(this.methodInfo));
99
+ }
100
+ suggestions = methodList?.map((item) => {
101
+ return {
102
+ detail: item?.description,
103
+ label: item?.name, // 显示的提示内容;默认情况下,这也是选择完成时插入的文本。
104
+ insertText: item.value?.startsWith('#') ? item.value?.slice(1) : item.value, // 选择此完成时应插入到文档中的字符串或片段
105
+ kind: monaco.languages.CompletionItemKind['Method'] // 此完成项的种类。编辑器根据图标的种类选择图标。
106
+ };
107
+ });
108
+
109
+ return {
110
+ suggestions
111
+ };
112
+ };
113
+
114
+ // 触发自动完成建议
115
+ triggerSuggest = (methodInfo) => {
116
+ if (!this.baseEditor?.editor) return;
117
+
118
+ if (this.suggestTime) {
119
+ clearTimeout(this.suggestTime);
120
+ }
121
+
122
+ this.suggestTime = setTimeout(() => {
123
+ this.methodInfo = methodInfo;
124
+ this.dispose = this.baseEditor.editor.trigger('', 'editor.action.triggerSuggest', {});
125
+ }, 0);
126
+ };
127
+
128
+ /**
129
+ * Cleans up resources by disposing the suggestion provider and clearing any pending suggestion timeouts.
130
+ * This method ensures proper cleanup of Monaco editor suggestion related resources.
131
+ */
132
+ dispose = () => {
133
+ if (this.disposable) {
134
+ this.disposable.dispose();
135
+ this.disposable = null;
136
+ }
137
+
138
+ if (this.suggestTime) {
139
+ clearTimeout(this.suggestTime);
140
+ this.suggestTime = null;
141
+ }
142
+ this.methodInfo = null;
143
+ };
144
+
145
+ clearSuggest = () => {
146
+ this.methodInfo = null;
147
+ };
148
+
149
+ updateOptions = (options) => {
150
+ if (options) {
151
+ this.options = {
152
+ ...(this.options || {}),
153
+ ...options
154
+ };
155
+ }
156
+ };
157
+ }
@@ -0,0 +1,119 @@
1
+ import { escapeRegExp } from '../core/BaseEditor';
2
+ const markList = ['>=', '<=', '!=', '=', '>', '<', '+', '-', '*', '/', '(', ')', ';', ',', ':', '{', '}', '.', '[', ']', '&&', '||'];
3
+
4
+ export const loadMyLanguage = ({ monaco, modeField = {}, defaultJsLang, languageId }) => {
5
+ const { tokenizer, ...lang } = defaultJsLang || {};
6
+ const { root, ...rest } = tokenizer || {};
7
+
8
+ const { codemirrorMethodList = [], codemirrorFieldList = [], codemirrorKeywordList = [], codemirrorNormalList = [] } = modeField || {};
9
+
10
+ // 对需要转义的特殊字符进行转义
11
+ const escapedMarkList = markList.map((name) => escapeRegExp(name));
12
+ const markPattern = escapedMarkList.map((name) => `(${name})`).join('|');
13
+
14
+ // 函数
15
+ const escapedFunctionList = codemirrorMethodList.map((name) => escapeRegExp(name));
16
+ const functionPattern = `(${escapedFunctionList.join('|')})`;
17
+
18
+ // 字段
19
+ const fieldEscapedMarkList = codemirrorFieldList?.map((name) => '@' + escapeRegExp(name));
20
+ const fieldRegexPattern = `(${fieldEscapedMarkList.join('|')})`;
21
+
22
+ // 关键字
23
+ const keyEscapedMarkList = codemirrorKeywordList?.map((name) => escapeRegExp(name));
24
+ const keyRegexPattern = `(${keyEscapedMarkList.join('|')})`;
25
+
26
+ // 关键字
27
+ const normalEscapedMarkList = codemirrorNormalList?.map((name) => escapeRegExp(name));
28
+ const normalRegexPattern = `(${normalEscapedMarkList.join('|')})`;
29
+
30
+ const modeFiledReg = [];
31
+
32
+ if (codemirrorKeywordList?.length) {
33
+ modeFiledReg.push([new RegExp(keyRegexPattern), 'keyword']);
34
+ }
35
+
36
+ if (codemirrorMethodList?.length) {
37
+ modeFiledReg.push([new RegExp(functionPattern), 'function-keyword']);
38
+ }
39
+
40
+ if (codemirrorNormalList?.length) {
41
+ modeFiledReg.push([new RegExp(normalRegexPattern), 'function-keyword']);
42
+ }
43
+
44
+ if (codemirrorFieldList?.length) {
45
+ modeFiledReg.push([new RegExp(`${fieldRegexPattern}`), 'field-keyword']);
46
+ }
47
+
48
+ function isLanguageRegistered(cLanguageId) {
49
+ const registeredLanguages = monaco.languages.getLanguages();
50
+ return registeredLanguages.some((language) => language.id === cLanguageId);
51
+ }
52
+
53
+ const isRegistered = isLanguageRegistered(languageId);
54
+ if (!isRegistered) {
55
+ monaco.languages.register({ id: languageId });
56
+
57
+ // 继承js的快捷方式等
58
+ monaco.languages.setLanguageConfiguration(languageId, {
59
+ comments: {
60
+ lineComment: '//',
61
+ blockComment: ['/*', '*/']
62
+ },
63
+ brackets: [
64
+ ['{', '}'],
65
+ ['[', ']'],
66
+ ['(', ')']
67
+ ],
68
+ autoClosingPairs: [
69
+ { open: '{', close: '}' },
70
+ { open: '[', close: ']' },
71
+ { open: '(', close: ')' },
72
+ { open: '"', close: '"' },
73
+ { open: '\'', close: '\'' }
74
+ ],
75
+ surroundingPairs: [
76
+ { open: '{', close: '}' },
77
+ { open: '[', close: ']' },
78
+ { open: '(', close: ')' },
79
+ { open: '"', close: '"' },
80
+ { open: '\'', close: '\'' }
81
+ ]
82
+ });
83
+ }
84
+ monaco.languages.setMonarchTokensProvider(languageId, {
85
+ // 继承 JavaScript 的大小写敏感性
86
+ // ignoreCase: true,
87
+ tokenizer: {
88
+ root: [
89
+ // 单行注释
90
+ { regex: /\/\/.*/, action: { token: 'comment' } },
91
+ { regex: /\/\*/, action: { token: 'comment', next: '@comment' } },
92
+ { regex: /\*\//, action: { token: 'comment', next: '@pop' } },
93
+ // 块注释
94
+ { regex: /\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//, action: { token: 'comment' } },
95
+
96
+ // 处理符号
97
+ [new RegExp(markPattern), { token: 'mark-keyword' }],
98
+ [/\b(true|false)\b/, { token: 'boolean-keyword' }],
99
+ // 添加你的自定义关键字
100
+ ...(modeFiledReg || []),
101
+ ...(root || []),
102
+
103
+ // 标识符规则(必须在错误规则之前)
104
+ { regex: /[a-zA-Z_]\w*/, action: { token: 'error-keyword' } },
105
+
106
+ // 错误关键字规则(放在最后,作为默认匹配)
107
+ { regex: /./, action: { token: 'error-keyword' } }
108
+ ],
109
+ // // 注释状态:处理多行注释 /* ... */
110
+ // comment: [
111
+ // { regex: /\*\//, token: "comment", next: "@pop" }, // 匹配结束标记,退出 comment 状态
112
+ // { regex: /[^*]+/, token: "comment" }, // 匹配非 * 内容
113
+ // { regex: /\*/, token: "comment" } // 匹配单独的 *
114
+ // ],
115
+ ...(rest || {})
116
+ },
117
+ ...lang
118
+ });
119
+ };
@@ -0,0 +1,30 @@
1
+ let hasDefinedTheme = false;
2
+ export const loadMyTheme = ({ monaco, defaultThemeColor }) => {
3
+ if (hasDefinedTheme) return;
4
+ // 定义一个新的主题,或者修改现有主题
5
+ monaco.editor.defineTheme('formulaTheme', {
6
+ base: 'vs-dark', // 可以是 'vs', 'vs-dark', 或 'hc-black'
7
+ inherit: true, // 是否继承基础主题的其他设置
8
+ rules: [
9
+ {
10
+ token: 'function-keyword',
11
+ foreground: '#03a9f4' // 关键字颜色
12
+ },
13
+ {
14
+ token: 'field-keyword',
15
+ foreground: '#ff9800' // 关键字颜色
16
+ },
17
+ {
18
+ token: 'mark-keyword',
19
+ foreground: '#ffffff'
20
+ },
21
+ { token: 'error-keyword', foreground: '#f14c4c', fontStyle: 'underline' } // 红色加粗
22
+ ],
23
+ colors: {
24
+ ...defaultThemeColor
25
+ }
26
+ });
27
+
28
+ monaco.editor.setTheme('formulaTheme');
29
+ hasDefinedTheme = true;
30
+ };
@@ -0,0 +1,72 @@
1
+ // 标记错误信息
2
+ const markMistake = (range, type, message, editor, monaco) => {
3
+ const { startLineNumber, endLineNumber, startColumn, endColumn } = range;
4
+ monaco.editor.setModelMarkers(editor.getModel(), 'eslint', [
5
+ {
6
+ startLineNumber,
7
+ endLineNumber,
8
+ startColumn,
9
+ endColumn,
10
+ severity: monaco.MarkerSeverity[type], // type可以是Error,Warning,Info
11
+ message
12
+ }
13
+ ]);
14
+ };
15
+
16
+ // 清除错误信息
17
+ const clearMistake = ({ monaco, editor }) => {
18
+ monaco.editor.setModelMarkers(editor.getModel(), 'eslint', []);
19
+ };
20
+
21
+ // 格式化代码
22
+ export const formatCustom = ({ needValue, format, editor, monaco }) => {
23
+ clearMistake({ editor, monaco });
24
+ try {
25
+ editor.setValue(format(editor.getValue()));
26
+ } catch (e) {
27
+ const { message } = e;
28
+ const list = message.split(' ');
29
+ const line = list.indexOf('line');
30
+ const column = list.indexOf('column');
31
+ markMistake(
32
+ {
33
+ startLineNumber: Number(list[line + 1]),
34
+ endLineNumber: Number(list[line + 1]),
35
+ startColumn: Number(list[column + 1]),
36
+ endColumn: Number(list[column + 1])
37
+ },
38
+ 'Error',
39
+ message,
40
+ editor,
41
+ monaco
42
+ );
43
+ }
44
+ if (needValue) {
45
+ return editor.getValue();
46
+ }
47
+ };
48
+
49
+ // Lint
50
+ export const validateInfo = ({ format, editor, monaco }) => {
51
+ clearMistake({ editor, monaco });
52
+ try {
53
+ format(editor.getValue());
54
+ } catch (e) {
55
+ const { message } = e;
56
+ const list = message.split(' ');
57
+ const line = list.indexOf('line');
58
+ const column = list.indexOf('column');
59
+ markMistake(
60
+ {
61
+ startLineNumber: Number(list[line + 1]),
62
+ endLineNumber: Number(list[line + 1]),
63
+ startColumn: Number(list[column + 1]),
64
+ endColumn: Number(list[column + 1])
65
+ },
66
+ 'Error',
67
+ message,
68
+ editor,
69
+ monaco
70
+ );
71
+ }
72
+ };
@@ -0,0 +1,91 @@
1
+ export function processSize(size) {
2
+ return !/^\d+$/.test(size) ? size : `${size}px`;
3
+ }
4
+
5
+ // 查找最近的\[\]
6
+ export const findNearestLeftParenthesisIndex = (str, rightParenthesisIndex) => {
7
+ let stack = [];
8
+ for (let i = 0; i < rightParenthesisIndex; i++) {
9
+ if (str[i] === '[') {
10
+ stack.push(i);
11
+ } else if (str[i] === ']') {
12
+ if (stack.length > 0) {
13
+ stack.pop();
14
+ }
15
+ }
16
+ }
17
+ if (stack.length > 0) {
18
+ return stack.pop();
19
+ }
20
+ return -1; // 如果没有匹配的左括号,返回 -1
21
+ };
22
+
23
+ export function noop() {}
24
+
25
+ /**
26
+ * 辅助方法:处理最终非.结尾的路径片段,提取目标父节点
27
+ * @param {string} processedPath - 已处理(不以.结尾)的路径片段
28
+ * @returns {string} 提取并转换后的父节点
29
+ */
30
+ export function getParentItem({ prevIsDotObj, fieldIndex, cursorBeforeOneChar }) {
31
+ let [prevParentItem, preNearItem] = [,];
32
+ let preIndex;
33
+ const getParentItemInner = ({ prevIsDotObj, fieldIndex, cursorBeforeOneChar }) => {
34
+ if (prevIsDotObj?.endsWith('.')) {
35
+ // 获取@xx[@xxx].@a strIndex 获取 @xxx的下标
36
+ const strIndex = cursorBeforeOneChar.substring(0, fieldIndex - 1).lastIndexOf('@', fieldIndex - 1) + 1;
37
+
38
+ if (prevIsDotObj.endsWith('].')) {
39
+ let leftQuoteIndex = findNearestLeftParenthesisIndex(cursorBeforeOneChar, fieldIndex - 2);
40
+ // @ 小于 [ ; @aa[@bb].
41
+ // preStr @aa[ 中[ 的下标
42
+ // lastIndex = @bb 的@
43
+ if (fieldIndex < leftQuoteIndex) {
44
+ // @aa[@bb].
45
+ // 获取@aa 从strIndex 到 匹配的[
46
+ // prevParentItem = cursorBeforeOneChar.substring(strIndex, leftQuoteIndex);
47
+ } else {
48
+ prevParentItem = cursorBeforeOneChar.substring(1, leftQuoteIndex);
49
+ fieldIndex = prevParentItem.lastIndexOf('@') + 1;
50
+ preIndex = fieldIndex;
51
+ prevParentItem = prevParentItem.substring(fieldIndex);
52
+ }
53
+ } else {
54
+ prevParentItem = cursorBeforeOneChar.substring(strIndex, fieldIndex - 1);
55
+ preIndex = strIndex - 1;
56
+ }
57
+ if (!preNearItem) {
58
+ preNearItem = prevParentItem;
59
+ }
60
+
61
+ if (preIndex > -1) {
62
+ return getParentItemInner({
63
+ prevIsDotObj: cursorBeforeOneChar.substring(0, preIndex),
64
+ fieldIndex: preIndex,
65
+ cursorBeforeOneChar
66
+ });
67
+ }
68
+ }
69
+ };
70
+
71
+ getParentItemInner({ prevIsDotObj, fieldIndex, cursorBeforeOneChar });
72
+ return { prevParentItem, preNearItem };
73
+ }
74
+
75
+ // 根据父节点递归拿到相应层级的数据
76
+ export const getChildList = ({ prevParentItem, preNearItem, fieldList }) => {
77
+ let preParentInfo = fieldList.find((item) => item.name.includes(prevParentItem));
78
+
79
+ let childInfo = preParentInfo;
80
+ const getChildListInner = (pp) => {
81
+ pp?.children?.forEach((item) => {
82
+ if (item.name.includes(preNearItem)) {
83
+ childInfo = item;
84
+ } else {
85
+ getChildListInner(item);
86
+ }
87
+ });
88
+ };
89
+ getChildListInner(preParentInfo);
90
+ return childInfo || fieldList;
91
+ };