@fc-components/monaco-editor 0.1.24 → 0.1.26

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.
@@ -11,6 +11,7 @@ export declare type Situation = {
11
11
  type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME';
12
12
  metricName?: string;
13
13
  otherLabels: Label[];
14
+ hasOperator: boolean;
14
15
  } | {
15
16
  type: 'IN_GROUPING';
16
17
  metricName: string;
@@ -17,6 +17,7 @@ interface PromQLEditorProps {
17
17
  onEnter?: (value: string) => void;
18
18
  onBlur?: (value: string) => void;
19
19
  editorDidMount?: (editor: monacoTypes.editor.IStandaloneCodeEditor) => void;
20
+ debugEscKey?: boolean;
20
21
  }
21
22
  export default function PromQLEditor(props: PromQLEditorProps & DataProviderParams): React.JSX.Element;
22
23
  export {};
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.24",
6
+ "version": "0.1.26",
7
7
  "license": "MIT",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/monaco-editor.esm.js",
@@ -122,8 +122,8 @@ async function getLabelNamesForCompletions(
122
122
  }));
123
123
  }
124
124
 
125
- async function getLabelNamesForSelectorCompletions(metric: string | undefined, otherLabels: Label[], dataProvider: DataProvider): Promise<Completion[]> {
126
- return getLabelNamesForCompletions(metric, '=', true, otherLabels, dataProvider);
125
+ async function getLabelNamesForSelectorCompletions(metric: string | undefined, hasOperator: boolean, otherLabels: Label[], dataProvider: DataProvider): Promise<Completion[]> {
126
+ return getLabelNamesForCompletions(metric, hasOperator ? '' : '=', true, otherLabels, dataProvider);
127
127
  }
128
128
 
129
129
  async function getLabelNamesForByCompletions(metric: string | undefined, otherLabels: Label[], dataProvider: DataProvider): Promise<Completion[]> {
@@ -186,7 +186,7 @@ export function getCompletions(situation: Situation, dataProvider: DataProvider)
186
186
  return Promise.resolve([...historyCompletions, ...FUNCTION_COMPLETIONS, ...metricNames]);
187
187
  }
188
188
  case 'IN_LABEL_SELECTOR_NO_LABEL_NAME':
189
- return getLabelNamesForSelectorCompletions(situation.metricName, situation.otherLabels, dataProvider);
189
+ return getLabelNamesForSelectorCompletions(situation.metricName, situation.hasOperator, situation.otherLabels, dataProvider);
190
190
  case 'IN_GROUPING':
191
191
  return getLabelNamesForByCompletions(situation.metricName, situation.otherLabels, dataProvider);
192
192
  case 'IN_LABEL_SELECTOR_WITH_LABEL_NAME':
@@ -132,6 +132,7 @@ export type Situation =
132
132
  type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME';
133
133
  metricName?: string;
134
134
  otherLabels: Label[];
135
+ hasOperator: boolean;
135
136
  }
136
137
  | {
137
138
  type: 'IN_GROUPING';
@@ -158,14 +159,38 @@ function isPathMatch(resolverPath: NodeTypeId[], cursorPath: number[]): boolean
158
159
  const ERROR_NODE_NAME: NodeTypeId = 0; // this is used as error-id
159
160
 
160
161
  const RESOLVERS: Resolver[] = [
162
+ {
163
+ path: [LabelName, UnquotedLabelMatcher],
164
+ fun: resolveLabelName,
165
+ },
166
+ {
167
+ path: [UnquotedLabelMatcher, LabelMatchers],
168
+ fun: resolveLabelName,
169
+ },
161
170
  {
162
171
  path: [LabelMatchers, VectorSelector],
163
172
  fun: resolveLabelKeysWithEquals,
164
173
  },
174
+ {
175
+ path: [LabelMatchers, VectorSelector, PromQL],
176
+ fun: resolveLabelKeysWithEquals,
177
+ },
165
178
  {
166
179
  path: [PromQL],
167
180
  fun: resolveTopLevel,
168
181
  },
182
+ {
183
+ path: [VectorSelector, PromQL],
184
+ fun: resolveTopLevel,
185
+ },
186
+ {
187
+ path: [Identifier, VectorSelector],
188
+ fun: resolveTopLevel,
189
+ },
190
+ {
191
+ path: [Identifier, VectorSelector, PromQL],
192
+ fun: resolveTopLevel,
193
+ },
169
194
  {
170
195
  path: [FunctionCallBody],
171
196
  fun: resolveInFunction,
@@ -180,7 +205,7 @@ const RESOLVERS: Resolver[] = [
180
205
  },
181
206
  {
182
207
  path: [ERROR_NODE_NAME, UnquotedLabelMatcher],
183
- fun: resolveLabelMatcher,
208
+ fun: resolveErrorInLabelMatcher,
184
209
  },
185
210
  {
186
211
  path: [ERROR_NODE_NAME, NumberDurationLiteralInDurationContext, MatrixSelector],
@@ -359,6 +384,32 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, _pos: number): Situ
359
384
  };
360
385
  }
361
386
 
387
+ function resolveErrorInLabelMatcher(node: SyntaxNode, text: string, pos: number): Situation | null {
388
+ // 处理错误节点在 UnquotedLabelMatcher 中的情况
389
+ // 需要判断是删除值还是删除名称
390
+
391
+ const parent = walk(node, [['parent', UnquotedLabelMatcher]]);
392
+ if (parent === null) {
393
+ return null;
394
+ }
395
+
396
+ const labelNameNode = walk(parent, [['firstChild', LabelName]]);
397
+
398
+ // 情况1: 有完整的标签名 -> 提示 label value
399
+ if (labelNameNode !== null) {
400
+ // 检查是否有 MatchOp(=, =~, !=, !~)
401
+ const hasMatchOp = walk(labelNameNode, [['nextSibling', MatchOp]]) !== null;
402
+ if (hasMatchOp) {
403
+ // 这是值场景,使用 resolveLabelMatcher 的逻辑
404
+ return resolveLabelMatcher(node, text, pos);
405
+ }
406
+ }
407
+
408
+ // 情况2: 没有完整的标签名或没有 MatchOp -> 提示 label name
409
+ // 使用 resolveLabelName 的逻辑
410
+ return resolveLabelName(parent, text, pos);
411
+ }
412
+
362
413
  function resolveTopLevel(): Situation {
363
414
  return {
364
415
  type: 'AT_ROOT',
@@ -377,6 +428,66 @@ function resolveDurations(): Situation {
377
428
  };
378
429
  }
379
430
 
431
+ function resolveLabelName(node: SyntaxNode, text: string, pos: number): Situation | null {
432
+ let labelMatcherNode: SyntaxNode | null = node;
433
+
434
+ // 如果节点是错误节点,尝试找到其父节点 UnquotedLabelMatcher
435
+ if (node.type.isError) {
436
+ const parent = node.parent;
437
+ if (parent !== null && parent.type.id === UnquotedLabelMatcher) {
438
+ labelMatcherNode = parent;
439
+ } else {
440
+ return null;
441
+ }
442
+ } else if (node.type.id === LabelName) {
443
+ labelMatcherNode = walk(node, [['parent', UnquotedLabelMatcher]]);
444
+ }
445
+
446
+ if (labelMatcherNode === null || labelMatcherNode.type.id !== UnquotedLabelMatcher) {
447
+ return null;
448
+ }
449
+
450
+ const labelNameNode = walk(labelMatcherNode, [['firstChild', LabelName]]);
451
+ if (labelNameNode === null) {
452
+ return null;
453
+ }
454
+
455
+ if (pos > labelNameNode.to) {
456
+ return null;
457
+ }
458
+
459
+ const labelMatchersNode = walk(labelMatcherNode, [['parent', LabelMatchers]]);
460
+ if (labelMatchersNode === null) {
461
+ return null;
462
+ }
463
+
464
+ const currentLabelName = getNodeText(labelNameNode, text);
465
+ const allLabels = getLabels(labelMatchersNode, text);
466
+ const otherLabels = allLabels.filter((label) => label.name !== currentLabelName);
467
+ const hasOperator = walk(labelNameNode, [['nextSibling', MatchOp]]) !== null;
468
+
469
+ const metricNameNode = walk(labelMatchersNode, [
470
+ ['parent', VectorSelector],
471
+ ['firstChild', Identifier],
472
+ ]);
473
+
474
+ if (metricNameNode === null) {
475
+ return {
476
+ type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
477
+ otherLabels,
478
+ hasOperator,
479
+ };
480
+ }
481
+
482
+ const metricName = getNodeText(metricNameNode, text);
483
+ return {
484
+ type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
485
+ metricName,
486
+ otherLabels,
487
+ hasOperator,
488
+ };
489
+ }
490
+
380
491
  function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number): Situation | null {
381
492
  // next false positive:
382
493
  // `something{a="1"^}`
@@ -407,6 +518,7 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
407
518
  return {
408
519
  type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
409
520
  otherLabels,
521
+ hasOperator: false,
410
522
  };
411
523
  }
412
524
 
@@ -416,6 +528,7 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
416
528
  type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME',
417
529
  metricName,
418
530
  otherLabels,
531
+ hasOperator: false,
419
532
  };
420
533
  }
421
534
 
@@ -471,8 +584,10 @@ export function getSituation(text: string, pos: number): Situation | null {
471
584
  const currentNode = cur.node;
472
585
 
473
586
  const ids = [cur.type.id];
587
+ // const names = [cur.type.name];
474
588
  while (cur.parent()) {
475
589
  ids.push(cur.type.id);
590
+ // names.push(cur.type.name);
476
591
  }
477
592
 
478
593
  for (let resolver of RESOLVERS) {
@@ -30,6 +30,7 @@ interface PromQLEditorProps {
30
30
  onEnter?: (value: string) => void;
31
31
  onBlur?: (value: string) => void;
32
32
  editorDidMount?: (editor: monacoTypes.editor.IStandaloneCodeEditor) => void;
33
+ debugEscKey?: boolean;
33
34
  }
34
35
 
35
36
  const PROMQL_LANG_ID = promLanguageDefinition.id;
@@ -103,11 +104,14 @@ export default function PromQLEditor(props: PromQLEditorProps & DataProviderPara
103
104
  onEnter,
104
105
  onBlur,
105
106
  editorDidMount,
107
+ debugEscKey = false,
106
108
  } = props;
107
109
  const autocompleteDisposeFun = useRef<(() => void) | null>(null);
108
110
  const containerRef = useRef<HTMLDivElement>(null);
109
111
  const dataProviderRef = useRef<DataProvider | null>(null);
110
112
  const editorRef = useRef<monacoTypes.editor.IStandaloneCodeEditor | null>(null);
113
+ const previousContentRef = useRef<string>('');
114
+ const lastDeletionTriggerTimeRef = useRef<number>(0);
111
115
  const styles = getStyles(placeholder);
112
116
  const handleEditorDidMount = (editor: monacoTypes.editor.IStandaloneCodeEditor) => {
113
117
  editorRef.current = editor;
@@ -209,6 +213,27 @@ export default function PromQLEditor(props: PromQLEditorProps & DataProviderPara
209
213
  '!suggestWidgetVisible && isEditorFocused' + id,
210
214
  );
211
215
 
216
+ editor.addCommand(
217
+ monaco.KeyCode.Escape,
218
+ () => {
219
+ if (debugEscKey) {
220
+ const suggestWidgetVisible = (editor as any)?._contextKeyService?.getContextKeyValue?.('suggestWidgetVisible');
221
+ // eslint-disable-next-line no-console
222
+ console.log('[PromQLEditor][ESC]', {
223
+ suggestWidgetVisible,
224
+ valueLength: editor.getValue().length,
225
+ });
226
+ }
227
+ editor.trigger('keyboard', 'hideSuggestWidget', {});
228
+ },
229
+ 'suggestWidgetVisible && isEditorFocused' + id,
230
+ );
231
+
232
+ // Initialize previous content tracking
233
+ previousContentRef.current = editor.getValue();
234
+ lastDeletionTriggerTimeRef.current = 0;
235
+ const DELETION_TRIGGER_DEBOUNCE_MS = 1000;
236
+
212
237
  editor.onDidChangeModelContent(() => {
213
238
  const model = editor.getModel();
214
239
  if (model) {
@@ -221,6 +246,23 @@ export default function PromQLEditor(props: PromQLEditorProps & DataProviderPara
221
246
  }));
222
247
 
223
248
  monaco.editor.setModelMarkers(model, 'owner', markers);
249
+
250
+ // Detect deletion and trigger completion
251
+ const previousContent = previousContentRef.current;
252
+ const isDeletion = query.length < previousContent.length;
253
+
254
+ if (isDeletion && enableAutocomplete) {
255
+ const now = Date.now();
256
+ if (now - lastDeletionTriggerTimeRef.current >= DELETION_TRIGGER_DEBOUNCE_MS) {
257
+ lastDeletionTriggerTimeRef.current = now;
258
+ // Trigger completion after deletion
259
+ setTimeout(() => {
260
+ editor.trigger('keyboard', 'editor.action.triggerSuggest', {});
261
+ }, 0);
262
+ }
263
+ }
264
+
265
+ previousContentRef.current = query;
224
266
  }
225
267
  });
226
268