@fc-components/monaco-editor 0.1.27 → 0.3.1
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/expr/__tests__/__mocks__/monaco-editor.d.ts +28 -0
- package/dist/expr/completion/getCompletionProvider.d.ts +4 -0
- package/dist/expr/expr.d.ts +83 -0
- package/dist/expr/index.d.ts +3 -0
- package/dist/expr/parser/index.d.ts +3 -0
- package/dist/expr/parser/lexer.d.ts +27 -0
- package/dist/expr/parser/parser.d.ts +66 -0
- package/dist/expr/parser/types.d.ts +32 -0
- package/dist/expr/types.d.ts +17 -0
- package/dist/expr/validation.d.ts +12 -0
- package/dist/index.d.ts +2 -0
- package/dist/monaco-editor.cjs.development.js +1986 -3
- package/dist/monaco-editor.cjs.development.js.map +1 -1
- package/dist/monaco-editor.cjs.production.min.js +1 -1
- package/dist/monaco-editor.cjs.production.min.js.map +1 -1
- package/dist/monaco-editor.esm.js +1989 -7
- package/dist/monaco-editor.esm.js.map +1 -1
- package/dist/promql/completion/situation.d.ts +2 -0
- package/package.json +6 -2
- package/src/expr/__tests__/__mocks__/monaco-editor.ts +34 -0
- package/src/expr/__tests__/expr.test.tsx +339 -0
- package/src/expr/completion/getCompletionProvider.ts +133 -0
- package/src/expr/expr.ts +229 -0
- package/src/expr/index.tsx +322 -0
- package/src/expr/parser/index.ts +3 -0
- package/src/expr/parser/lexer.ts +377 -0
- package/src/expr/parser/parser.ts +581 -0
- package/src/expr/parser/types.ts +77 -0
- package/src/expr/types.ts +17 -0
- package/src/expr/validation.ts +209 -0
- package/src/index.tsx +2 -0
- package/src/promql/__tests__/completions.test.ts +72 -0
- package/src/promql/__tests__/situation.test.ts +85 -0
- package/src/promql/completion/completions.ts +11 -2
- package/src/promql/completion/situation.ts +65 -1
- package/src/promql/promql.ts +3 -1
package/src/expr/expr.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
export const languageConfiguration = {
|
|
2
|
+
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
|
|
3
|
+
comments: {
|
|
4
|
+
lineComment: '//',
|
|
5
|
+
blockComment: ['/*', '*/'] as [string, string],
|
|
6
|
+
},
|
|
7
|
+
brackets: [
|
|
8
|
+
['{', '}'],
|
|
9
|
+
['[', ']'],
|
|
10
|
+
['(', ')'],
|
|
11
|
+
] as any,
|
|
12
|
+
autoClosingPairs: [
|
|
13
|
+
{ open: '{', close: '}' },
|
|
14
|
+
{ open: '[', close: ']' },
|
|
15
|
+
{ open: '(', close: ')' },
|
|
16
|
+
{ open: '"', close: '"' },
|
|
17
|
+
{ open: "'", close: "'" },
|
|
18
|
+
{ open: '`', close: '`' },
|
|
19
|
+
] as any,
|
|
20
|
+
surroundingPairs: [
|
|
21
|
+
{ open: '{', close: '}' },
|
|
22
|
+
{ open: '[', close: ']' },
|
|
23
|
+
{ open: '(', close: ')' },
|
|
24
|
+
{ open: '"', close: '"' },
|
|
25
|
+
{ open: "'", close: "'" },
|
|
26
|
+
{ open: '`', close: '`' },
|
|
27
|
+
] as any,
|
|
28
|
+
folding: {
|
|
29
|
+
offSide: false,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Expr-lang keywords
|
|
34
|
+
const keywords = ['let', 'true', 'false', 'nil', 'in', 'not', 'and', 'or', 'if', 'else'];
|
|
35
|
+
|
|
36
|
+
// Expr-lang built-in functions
|
|
37
|
+
const stringFunctions = [
|
|
38
|
+
'trim',
|
|
39
|
+
'trimPrefix',
|
|
40
|
+
'trimSuffix',
|
|
41
|
+
'upper',
|
|
42
|
+
'lower',
|
|
43
|
+
'split',
|
|
44
|
+
'splitAfter',
|
|
45
|
+
'replace',
|
|
46
|
+
'repeat',
|
|
47
|
+
'indexOf',
|
|
48
|
+
'lastIndexOf',
|
|
49
|
+
'hasPrefix',
|
|
50
|
+
'hasSuffix',
|
|
51
|
+
'contains',
|
|
52
|
+
'startsWith',
|
|
53
|
+
'endsWith',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const dateFunctions = ['now', 'duration', 'date', 'timezone'];
|
|
57
|
+
|
|
58
|
+
const numberFunctions = ['max', 'min', 'abs', 'ceil', 'floor', 'round'];
|
|
59
|
+
|
|
60
|
+
const arrayFunctions = [
|
|
61
|
+
'all',
|
|
62
|
+
'any',
|
|
63
|
+
'one',
|
|
64
|
+
'none',
|
|
65
|
+
'map',
|
|
66
|
+
'filter',
|
|
67
|
+
'find',
|
|
68
|
+
'findIndex',
|
|
69
|
+
'findLast',
|
|
70
|
+
'findLastIndex',
|
|
71
|
+
'groupBy',
|
|
72
|
+
'count',
|
|
73
|
+
'concat',
|
|
74
|
+
'flatten',
|
|
75
|
+
'uniq',
|
|
76
|
+
'join',
|
|
77
|
+
'reduce',
|
|
78
|
+
'sum',
|
|
79
|
+
'mean',
|
|
80
|
+
'median',
|
|
81
|
+
'first',
|
|
82
|
+
'last',
|
|
83
|
+
'take',
|
|
84
|
+
'reverse',
|
|
85
|
+
'sort',
|
|
86
|
+
'sortBy',
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const mapFunctions = ['keys', 'values'];
|
|
90
|
+
|
|
91
|
+
const typeConversionFunctions = ['type', 'int', 'float', 'string', 'toJSON', 'fromJSON', 'toBase64', 'fromBase64', 'toPairs', 'fromPairs'];
|
|
92
|
+
|
|
93
|
+
const miscFunctions = ['len', 'get'];
|
|
94
|
+
|
|
95
|
+
const bitwiseFunctions = ['bitand', 'bitor', 'bitxor', 'bitnand', 'bitnot', 'bitshl', 'bitshr', 'bitushr'];
|
|
96
|
+
|
|
97
|
+
const builtinFunctions = [
|
|
98
|
+
...stringFunctions,
|
|
99
|
+
...dateFunctions,
|
|
100
|
+
...numberFunctions,
|
|
101
|
+
...arrayFunctions,
|
|
102
|
+
...mapFunctions,
|
|
103
|
+
...typeConversionFunctions,
|
|
104
|
+
...miscFunctions,
|
|
105
|
+
...bitwiseFunctions,
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
export const language = {
|
|
109
|
+
defaultToken: '',
|
|
110
|
+
tokenPostfix: '.expr',
|
|
111
|
+
|
|
112
|
+
brackets: [
|
|
113
|
+
{ open: '(', close: ')', token: 'delimiter.parenthesis' },
|
|
114
|
+
{ open: '{', close: '}', token: 'delimiter.curly' },
|
|
115
|
+
{ open: '[', close: ']', token: 'delimiter.square' },
|
|
116
|
+
],
|
|
117
|
+
|
|
118
|
+
keywords,
|
|
119
|
+
|
|
120
|
+
builtinFunctions,
|
|
121
|
+
|
|
122
|
+
operators: ['+', '-', '*', '/', '%', '^', '**', '==', '!=', '<', '>', '<=', '>=', '!', '&&', '||', '?:', '??', '.', '?.', 'in', 'matches', '..', '|'],
|
|
123
|
+
|
|
124
|
+
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,2}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
|
|
125
|
+
|
|
126
|
+
digits: /\d+(_+\d+)*/,
|
|
127
|
+
|
|
128
|
+
hexdigits: /[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
|
|
129
|
+
|
|
130
|
+
octdigits: /[0-7]+(_+[0-7]+)*/,
|
|
131
|
+
|
|
132
|
+
bindigits: /[01]+(_+[01]+)*/,
|
|
133
|
+
|
|
134
|
+
tokenizer: {
|
|
135
|
+
root: [
|
|
136
|
+
{ include: '@whitespace' },
|
|
137
|
+
{ include: '@comments' },
|
|
138
|
+
{ include: '@numbers' },
|
|
139
|
+
{ include: '@strings' },
|
|
140
|
+
{ include: '@bytes' },
|
|
141
|
+
|
|
142
|
+
// Function calls: identifier followed by '('
|
|
143
|
+
[/[a-zA-Z_]\w*(?=\s*\()/, { cases: { '@builtinFunctions': 'keyword.function', '@default': 'identifier.function' } }],
|
|
144
|
+
|
|
145
|
+
// Keywords and identifiers
|
|
146
|
+
[/[a-zA-Z_]\w*/, { cases: { '@keywords': 'keyword', '@default': 'identifier' } }],
|
|
147
|
+
|
|
148
|
+
// Operators
|
|
149
|
+
[/[?][?:]/, 'operator'], // ??, ?:
|
|
150
|
+
[/[?][.]/, 'operator'], // ?.
|
|
151
|
+
[/[.]{2}/, 'operator'], // ..
|
|
152
|
+
[/[*]{2}/, 'operator'], // **
|
|
153
|
+
[/[|]/, 'operator'], // |
|
|
154
|
+
[/[+\-*/%^]/, 'operator'],
|
|
155
|
+
[/==|!=|<=|>=|<|>/, 'operator'],
|
|
156
|
+
[/!|&&|\|\|/, 'operator'],
|
|
157
|
+
[/[=]/, 'operator'],
|
|
158
|
+
[/[.~]/, 'operator'],
|
|
159
|
+
|
|
160
|
+
// Delimiters
|
|
161
|
+
[/[,;]/, 'delimiter'],
|
|
162
|
+
[/[{}()\[\]]/, '@brackets'],
|
|
163
|
+
],
|
|
164
|
+
|
|
165
|
+
whitespace: [[/[ \t\r\n]+/, 'white']],
|
|
166
|
+
|
|
167
|
+
comments: [
|
|
168
|
+
[/\/\/.*$/, 'comment'],
|
|
169
|
+
[/\/\*/, { token: 'comment.quote', next: '@comment' }],
|
|
170
|
+
],
|
|
171
|
+
|
|
172
|
+
comment: [
|
|
173
|
+
[/[^*/]+/, 'comment'],
|
|
174
|
+
[/\*\//, { token: 'comment.quote', next: '@pop' }],
|
|
175
|
+
[/./, 'comment'],
|
|
176
|
+
],
|
|
177
|
+
|
|
178
|
+
numbers: [
|
|
179
|
+
[/0[xX]@hexdigits/, 'number.hex'],
|
|
180
|
+
[/0[oO]@octdigits/, 'number.octal'],
|
|
181
|
+
[/0[bB]@bindigits/, 'number.binary'],
|
|
182
|
+
[/(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?/, 'number'],
|
|
183
|
+
],
|
|
184
|
+
|
|
185
|
+
strings: [
|
|
186
|
+
[/"/, { token: 'string.double', next: '@string_double' }],
|
|
187
|
+
[/'/, { token: 'string', next: '@string_single' }],
|
|
188
|
+
[/`/, { token: 'string.backtick', next: '@string_backtick' }],
|
|
189
|
+
],
|
|
190
|
+
|
|
191
|
+
string_double: [
|
|
192
|
+
[/[^"\\]+/, 'string.double'],
|
|
193
|
+
[/@escapes/, 'string.escape'],
|
|
194
|
+
[/\\./, 'string.escape.invalid'],
|
|
195
|
+
[/"/, { token: 'string.double', next: '@pop' }],
|
|
196
|
+
],
|
|
197
|
+
|
|
198
|
+
string_single: [
|
|
199
|
+
[/[^'\\]+/, 'string'],
|
|
200
|
+
[/@escapes/, 'string.escape'],
|
|
201
|
+
[/\\./, 'string.escape.invalid'],
|
|
202
|
+
[/'/, { token: 'string', next: '@pop' }],
|
|
203
|
+
],
|
|
204
|
+
|
|
205
|
+
string_backtick: [
|
|
206
|
+
[/[^`]+/, 'string.backtick'],
|
|
207
|
+
[/`/, { token: 'string.backtick', next: '@pop' }],
|
|
208
|
+
],
|
|
209
|
+
|
|
210
|
+
bytes: [
|
|
211
|
+
[/[bB]"/, { token: 'string.bytes', next: '@bytes_double' }],
|
|
212
|
+
[/[bB]'/, { token: 'string.bytes', next: '@bytes_single' }],
|
|
213
|
+
],
|
|
214
|
+
|
|
215
|
+
bytes_double: [
|
|
216
|
+
[/[^"\\]+/, 'string.bytes'],
|
|
217
|
+
[/\\(?:[abfnrtv\\"]|x[0-9A-Fa-f]{2}|[0-7]{3})/, 'string.escape'],
|
|
218
|
+
[/\\./, 'string.escape.invalid'],
|
|
219
|
+
[/"/, { token: 'string.bytes', next: '@pop' }],
|
|
220
|
+
],
|
|
221
|
+
|
|
222
|
+
bytes_single: [
|
|
223
|
+
[/[^'\\]+/, 'string.bytes'],
|
|
224
|
+
[/\\(?:[abfnrtv\\']|x[0-9A-Fa-f]{2}|[0-7]{3})/, 'string.escape'],
|
|
225
|
+
[/\\./, 'string.escape.invalid'],
|
|
226
|
+
[/'/, { token: 'string.bytes', next: '@pop' }],
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
};
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import MonacoEditor from 'react-monaco-editor';
|
|
3
|
+
import * as monaco from 'monaco-editor';
|
|
4
|
+
import type * as monacoTypes from 'monaco-editor/esm/vs/editor/editor.api';
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import { css } from '@emotion/css';
|
|
7
|
+
import { language, languageConfiguration } from './expr';
|
|
8
|
+
import { getExprCompletionProvider } from './completion/getCompletionProvider';
|
|
9
|
+
import { validateExpr } from './validation';
|
|
10
|
+
import type { ExprEditorProps } from './types';
|
|
11
|
+
|
|
12
|
+
const EXPR_LANG_ID = 'expr';
|
|
13
|
+
const SIZE_MAP: Record<
|
|
14
|
+
string,
|
|
15
|
+
{
|
|
16
|
+
className: string;
|
|
17
|
+
top: number;
|
|
18
|
+
bottom: number;
|
|
19
|
+
minHeight: number;
|
|
20
|
+
}
|
|
21
|
+
> = {
|
|
22
|
+
small: {
|
|
23
|
+
className: 'ant-input-sm',
|
|
24
|
+
top: 1,
|
|
25
|
+
bottom: 1,
|
|
26
|
+
minHeight: 24,
|
|
27
|
+
},
|
|
28
|
+
middle: {
|
|
29
|
+
className: 'ant-input-md',
|
|
30
|
+
top: 1,
|
|
31
|
+
bottom: 1,
|
|
32
|
+
minHeight: 32,
|
|
33
|
+
},
|
|
34
|
+
large: {
|
|
35
|
+
className: 'ant-input-lg',
|
|
36
|
+
top: 3,
|
|
37
|
+
bottom: 2,
|
|
38
|
+
minHeight: 40,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const themeMap: Record<string, string> = {
|
|
43
|
+
light: 'expr-light',
|
|
44
|
+
dark: 'expr-dark',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const containerDisabledClassName = css`
|
|
48
|
+
.monaco-editor {
|
|
49
|
+
user-select: none;
|
|
50
|
+
pointer-events: none;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const containerReadOnlyClassName = css`
|
|
55
|
+
.monaco-editor .cursors-layer > .cursor {
|
|
56
|
+
opacity: 0 !important;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
export default function ExprEditor(props: ExprEditorProps) {
|
|
61
|
+
const id = uuidv4();
|
|
62
|
+
const {
|
|
63
|
+
className,
|
|
64
|
+
maxHeight,
|
|
65
|
+
fontSize,
|
|
66
|
+
size = 'middle',
|
|
67
|
+
theme = 'light',
|
|
68
|
+
value = '',
|
|
69
|
+
placeholder,
|
|
70
|
+
enableAutocomplete = true,
|
|
71
|
+
readOnly = false,
|
|
72
|
+
disabled = false,
|
|
73
|
+
onChange,
|
|
74
|
+
onEnter,
|
|
75
|
+
onBlur,
|
|
76
|
+
onFocus,
|
|
77
|
+
editorDidMount,
|
|
78
|
+
} = props;
|
|
79
|
+
|
|
80
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
81
|
+
const editorRef = useRef<monacoTypes.editor.IStandaloneCodeEditor | null>(null);
|
|
82
|
+
const modelRef = useRef<monaco.editor.ITextModel | null>(null);
|
|
83
|
+
const disposablesRef = useRef<monaco.IDisposable[]>([]);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
// Register language
|
|
87
|
+
if (!monaco.languages.getLanguages().some((lang) => lang.id === EXPR_LANG_ID)) {
|
|
88
|
+
monaco.languages.register({ id: EXPR_LANG_ID });
|
|
89
|
+
monaco.languages.setMonarchTokensProvider(EXPR_LANG_ID, language as any);
|
|
90
|
+
monaco.languages.setLanguageConfiguration(EXPR_LANG_ID, languageConfiguration);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Register completion provider
|
|
94
|
+
if (enableAutocomplete) {
|
|
95
|
+
const disposable = monaco.languages.registerCompletionItemProvider(EXPR_LANG_ID, getExprCompletionProvider());
|
|
96
|
+
disposablesRef.current.push(disposable);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
disposablesRef.current.forEach((disposable) => disposable.dispose());
|
|
101
|
+
disposablesRef.current = [];
|
|
102
|
+
};
|
|
103
|
+
}, [enableAutocomplete]);
|
|
104
|
+
|
|
105
|
+
const handleEditorMount = (editor: monacoTypes.editor.IStandaloneCodeEditor) => {
|
|
106
|
+
editorRef.current = editor;
|
|
107
|
+
modelRef.current = editor.getModel();
|
|
108
|
+
|
|
109
|
+
monaco.editor.defineTheme('expr-light', {
|
|
110
|
+
base: 'vs',
|
|
111
|
+
inherit: true,
|
|
112
|
+
rules: [],
|
|
113
|
+
colors: {
|
|
114
|
+
'editor.background': '#00000000',
|
|
115
|
+
focusBorder: '#00000000',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
monaco.editor.defineTheme('expr-dark', {
|
|
120
|
+
base: 'vs-dark',
|
|
121
|
+
inherit: true,
|
|
122
|
+
rules: [],
|
|
123
|
+
colors: {
|
|
124
|
+
'editor.background': '#00000000',
|
|
125
|
+
focusBorder: '#00000000',
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const isEditorFocused = editor.createContextKey<boolean>('isEditorFocused' + id, false);
|
|
130
|
+
|
|
131
|
+
editor.onDidBlurEditorWidget(() => {
|
|
132
|
+
isEditorFocused.set(false);
|
|
133
|
+
onBlur?.(editor.getValue());
|
|
134
|
+
const position = editor.getPosition();
|
|
135
|
+
if (position) {
|
|
136
|
+
const newSelection = new monaco.Selection(position.lineNumber, position.column, position.lineNumber, position.column);
|
|
137
|
+
editor.setSelection(newSelection);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
editor.onDidFocusEditorText(() => {
|
|
142
|
+
isEditorFocused.set(true);
|
|
143
|
+
onFocus?.(editor.getValue());
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Auto-height
|
|
147
|
+
const updateElementHeight = () => {
|
|
148
|
+
const containerDiv = containerRef.current;
|
|
149
|
+
if (containerDiv !== null) {
|
|
150
|
+
const pixelHeight = editor.getContentHeight();
|
|
151
|
+
containerDiv.style.minHeight = `${pixelHeight}px`;
|
|
152
|
+
containerDiv.style.width = '100%';
|
|
153
|
+
const pixelWidth = containerDiv.clientWidth;
|
|
154
|
+
editor.layout({ width: pixelWidth, height: pixelHeight });
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
editor.onDidContentSizeChange(updateElementHeight);
|
|
159
|
+
updateElementHeight();
|
|
160
|
+
|
|
161
|
+
// Disable search box
|
|
162
|
+
monaco.editor.addKeybindingRule({
|
|
163
|
+
keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyF,
|
|
164
|
+
command: null,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Shift+Enter for newline
|
|
168
|
+
editor.addCommand(
|
|
169
|
+
monaco.KeyMod.Shift | monaco.KeyCode.Enter,
|
|
170
|
+
() => {
|
|
171
|
+
const position = editor.getPosition();
|
|
172
|
+
if (position) {
|
|
173
|
+
editor.executeEdits('shift-enter', [
|
|
174
|
+
{
|
|
175
|
+
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
|
|
176
|
+
text: '\n',
|
|
177
|
+
},
|
|
178
|
+
]);
|
|
179
|
+
editor.setPosition({
|
|
180
|
+
lineNumber: position.lineNumber + 1,
|
|
181
|
+
column: 1,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
'isEditorFocused' + id,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Prevent default Enter
|
|
189
|
+
monaco.editor.addKeybindingRule({
|
|
190
|
+
keybinding: monaco.KeyCode.Enter,
|
|
191
|
+
command: '-',
|
|
192
|
+
when: '!suggestWidgetVisible',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Custom Enter handler
|
|
196
|
+
editor.addCommand(
|
|
197
|
+
monaco.KeyCode.Enter,
|
|
198
|
+
() => {
|
|
199
|
+
onEnter?.(editor.getValue());
|
|
200
|
+
},
|
|
201
|
+
'!suggestWidgetVisible && isEditorFocused' + id,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Setup validation on content change using decorations (no marker hover clutter)
|
|
205
|
+
const model = editor.getModel();
|
|
206
|
+
let errorDecorations: string[] = [];
|
|
207
|
+
|
|
208
|
+
if (model) {
|
|
209
|
+
const updateDecorations = () => {
|
|
210
|
+
const exprValue = model.getValue();
|
|
211
|
+
const markers = validateExpr(exprValue);
|
|
212
|
+
|
|
213
|
+
const newDecorations: monaco.editor.IModelDeltaDecoration[] = markers.map((m) => ({
|
|
214
|
+
range: new monaco.Range(
|
|
215
|
+
m.startLineNumber,
|
|
216
|
+
m.startColumn,
|
|
217
|
+
m.endLineNumber,
|
|
218
|
+
m.endColumn,
|
|
219
|
+
),
|
|
220
|
+
options: {
|
|
221
|
+
className: 'expr-error-squiggly',
|
|
222
|
+
hoverMessage: { value: m.message },
|
|
223
|
+
minimap: { color: '#e51400', position: 1 as monaco.editor.MinimapPosition },
|
|
224
|
+
overviewRuler: { color: '#e51400', position: monaco.editor.OverviewRulerLane.Right },
|
|
225
|
+
},
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
errorDecorations = model.deltaDecorations(errorDecorations, newDecorations);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const validateDisposable = model.onDidChangeContent(updateDecorations);
|
|
232
|
+
disposablesRef.current.push(validateDisposable);
|
|
233
|
+
|
|
234
|
+
// Run initial validation
|
|
235
|
+
updateDecorations();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Inject CSS for the red squiggly underline
|
|
239
|
+
const styleEl = document.createElement('style');
|
|
240
|
+
styleEl.textContent = `
|
|
241
|
+
.expr-error-squiggly {
|
|
242
|
+
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 6 3' preserveAspectRatio='none'%3E%3Cpath d='M0,2.5 L1.5,1 L3,2.5 L4.5,1 L6,2.5' stroke='%23e51400' stroke-width='0.6' fill='none'/%3E%3C/svg%3E") repeat-x left bottom;
|
|
243
|
+
background-size: 6px 3px;
|
|
244
|
+
padding-bottom: 3px;
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
document.head.appendChild(styleEl);
|
|
248
|
+
disposablesRef.current.push({ dispose: () => styleEl.remove() });
|
|
249
|
+
|
|
250
|
+
editorDidMount?.(editor);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const handleChange = (newValue: string, _e: monacoTypes.editor.IModelContentChangedEvent) => {
|
|
254
|
+
onChange?.(newValue);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const themeValue = themeMap[theme];
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div
|
|
261
|
+
className={
|
|
262
|
+
'ant-input' +
|
|
263
|
+
(size ? ` ${SIZE_MAP[size].className}` : '') +
|
|
264
|
+
(disabled ? ` ant-input-disabled ${containerDisabledClassName}` : '') +
|
|
265
|
+
(readOnly ? ` ${containerReadOnlyClassName}` : '') +
|
|
266
|
+
(className ? ` ${className}` : '')
|
|
267
|
+
}
|
|
268
|
+
style={{
|
|
269
|
+
display: 'block',
|
|
270
|
+
resize: 'vertical',
|
|
271
|
+
overflow: 'auto',
|
|
272
|
+
minHeight: SIZE_MAP[size].minHeight,
|
|
273
|
+
maxHeight,
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<div
|
|
277
|
+
ref={containerRef}
|
|
278
|
+
style={{
|
|
279
|
+
height: '100%',
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
<MonacoEditor
|
|
283
|
+
language={EXPR_LANG_ID}
|
|
284
|
+
theme={themeValue}
|
|
285
|
+
value={value}
|
|
286
|
+
onChange={handleChange}
|
|
287
|
+
options={{
|
|
288
|
+
placeholder: placeholder,
|
|
289
|
+
selectOnLineNumbers: true,
|
|
290
|
+
fontSize: fontSize || 12,
|
|
291
|
+
roundedSelection: false,
|
|
292
|
+
scrollBeyondLastLine: false,
|
|
293
|
+
readOnly: readOnly || disabled,
|
|
294
|
+
minimap: { enabled: false },
|
|
295
|
+
lineNumbers: 'off',
|
|
296
|
+
lineNumbersMinChars: 0,
|
|
297
|
+
glyphMargin: false,
|
|
298
|
+
folding: false,
|
|
299
|
+
lineDecorationsWidth: 0,
|
|
300
|
+
overviewRulerLanes: 0,
|
|
301
|
+
overviewRulerBorder: false,
|
|
302
|
+
hideCursorInOverviewRuler: true,
|
|
303
|
+
hover: {
|
|
304
|
+
enabled: true,
|
|
305
|
+
delay: 200,
|
|
306
|
+
},
|
|
307
|
+
fixedOverflowWidgets: true,
|
|
308
|
+
renderLineHighlight: 'none',
|
|
309
|
+
renderValidationDecorations: 'on',
|
|
310
|
+
scrollbar: {
|
|
311
|
+
vertical: 'hidden',
|
|
312
|
+
horizontal: 'auto',
|
|
313
|
+
},
|
|
314
|
+
automaticLayout: true,
|
|
315
|
+
wordWrap: 'on',
|
|
316
|
+
}}
|
|
317
|
+
editorDidMount={handleEditorMount}
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
}
|