-
+
);
}
@@ -249,6 +164,14 @@ export const DataLinkInput = memo(
DataLinkInput.displayName = 'DataLinkInput';
-function getElementPosition(suggestionElement: HTMLElement | null, activeIndex: number) {
- return (suggestionElement?.clientHeight ?? 0) * activeIndex;
-}
+const getStyles = (theme: GrafanaTheme2) => {
+ const baseInputStyles = getInputStyles({ theme, invalid: false }).input;
+
+ return {
+ container: css({
+ position: 'relative',
+ width: '100%',
+ }),
+ input: css(baseInputStyles),
+ };
+};
diff --git a/packages/grafana-ui/src/components/DataLinks/codemirrorUtils.test.ts b/packages/grafana-ui/src/components/DataLinks/codemirrorUtils.test.ts
new file mode 100644
index 00000000000..d63a052c804
--- /dev/null
+++ b/packages/grafana-ui/src/components/DataLinks/codemirrorUtils.test.ts
@@ -0,0 +1,412 @@
+import { CompletionContext, CompletionResult } from '@codemirror/autocomplete';
+import { EditorState } from '@codemirror/state';
+
+import { DataLinkBuiltInVars, VariableOrigin, VariableSuggestion } from '@grafana/data';
+
+import { dataLinkAutocompletion } from './codemirrorUtils';
+
+describe('dataLinkAutocompletion', () => {
+ const mockSuggestions: VariableSuggestion[] = [
+ {
+ value: DataLinkBuiltInVars.seriesName,
+ label: '__series.name',
+ documentation: 'Series name',
+ origin: VariableOrigin.Series,
+ },
+ {
+ value: DataLinkBuiltInVars.fieldName,
+ label: '__field.name',
+ documentation: 'Field name',
+ origin: VariableOrigin.Field,
+ },
+ {
+ value: 'myVar',
+ label: 'myVar',
+ documentation: 'Custom variable',
+ origin: VariableOrigin.Template,
+ },
+ {
+ value: DataLinkBuiltInVars.includeVars,
+ label: '__all_variables',
+ documentation: 'Include all variables',
+ origin: VariableOrigin.BuiltIn,
+ },
+ ];
+
+ // Helper function to create a mock CompletionContext
+ function createMockContext(text: string, pos: number, explicit = false): CompletionContext {
+ const state = EditorState.create({ doc: text });
+
+ return {
+ state,
+ pos,
+ explicit,
+ matchBefore: (regex: RegExp) => {
+ const textBefore = text.slice(0, pos);
+ const match = textBefore.match(regex);
+ if (match) {
+ return {
+ from: pos - match[0].length,
+ to: pos,
+ text: match[0],
+ };
+ }
+ return null;
+ },
+ tokenBefore: jest.fn().mockReturnValue(null),
+ aborted: false,
+ addEventListener: jest.fn(),
+ };
+ }
+
+ describe('with no suggestions', () => {
+ it('should return null when suggestions array is empty', () => {
+ const autocompletion = dataLinkAutocompletion([]);
+ const context = createMockContext('$', 1);
+ const result = autocompletion(context);
+
+ expect(result).toBeNull();
+ });
+ });
+
+ describe('explicit completion (Ctrl+Space)', () => {
+ it('should show all suggestions at cursor position when triggered explicitly', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://grafana.com', 19, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(19);
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should include proper labels and details for all suggestions', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 4, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result.options[0]).toMatchObject({
+ label: '__series.name',
+ detail: VariableOrigin.Series,
+ info: 'Series name',
+ type: 'variable',
+ });
+ expect(result.options[1]).toMatchObject({
+ label: '__field.name',
+ detail: VariableOrigin.Field,
+ info: 'Field name',
+ type: 'variable',
+ });
+ });
+
+ it('should apply template variables with :queryparam suffix', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 4, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ const templateVar = result.options.find((opt) => opt.label === 'myVar');
+ expect(templateVar?.apply).toBe('${myVar:queryparam}');
+ });
+
+ it('should apply non-template variables without :queryparam suffix', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 4, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ const seriesVar = result.options.find((opt) => opt.label === '__series.name');
+ expect(seriesVar?.apply).toBe(`\${${DataLinkBuiltInVars.seriesName}}`);
+ });
+
+ it('should apply includeVars without :queryparam suffix', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 4, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ const includeVars = result.options.find((opt) => opt.label === '__all_variables');
+ expect(includeVars?.apply).toBe(`\${${DataLinkBuiltInVars.includeVars}}`);
+ });
+ });
+
+ describe('trigger character matching', () => {
+ it('should return null when no trigger character is present', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://grafana.com', 19);
+ const result = autocompletion(context);
+
+ expect(result).toBeNull();
+ });
+
+ it('should return null when text does not start with $ or =', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 4);
+ const result = autocompletion(context);
+
+ expect(result).toBeNull();
+ });
+ });
+
+ describe('$ trigger character', () => {
+ it('should show completions when $ is typed', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('$', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should show completions when ${ is typed', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${', 2);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should show completions when typing partial variable name', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${my', 4);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should show completions with dots in variable name', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${__series.name', 15);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should position completion from current position for single $', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('$', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result.from).toBe(1);
+ });
+
+ it('should position completion from $ for partial match', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${test', 6);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result.from).toBe(0);
+ });
+ });
+
+ describe('= trigger character', () => {
+ it('should show completions when = is typed', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('=', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should show completions after = in URL query parameter', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://example.com?param=', 26);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should position completion from current position for single =', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('=', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result.from).toBe(1);
+ });
+ });
+
+ describe('variable application', () => {
+ it('should use custom apply function for single character trigger', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('$', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(typeof result.options[0].apply).toBe('function');
+ });
+
+ it('should use string apply for multi-character match', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${test', 6);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(typeof result.options[0].apply).toBe('string');
+ });
+
+ it('should apply correct variable syntax for Series origin', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${test', 6);
+ const result = autocompletion(context) as CompletionResult;
+
+ const seriesVar = result.options.find((opt) => opt.label === '__series.name');
+ expect(seriesVar?.apply).toBe(`\${${DataLinkBuiltInVars.seriesName}}`);
+ });
+
+ it('should apply correct variable syntax for Field origin', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${test', 6);
+ const result = autocompletion(context) as CompletionResult;
+
+ const fieldVar = result.options.find((opt) => opt.label === '__field.name');
+ expect(fieldVar?.apply).toBe(`\${${DataLinkBuiltInVars.fieldName}}`);
+ });
+
+ it('should apply correct variable syntax for Template origin', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${test', 6);
+ const result = autocompletion(context) as CompletionResult;
+
+ const templateVar = result.options.find((opt) => opt.label === 'myVar');
+ expect(templateVar?.apply).toBe('${myVar:queryparam}');
+ });
+
+ it('should apply correct variable syntax for includeVars built-in', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${test', 6);
+ const result = autocompletion(context) as CompletionResult;
+
+ const includeVars = result.options.find((opt) => opt.label === '__all_variables');
+ expect(includeVars?.apply).toBe(`\${${DataLinkBuiltInVars.includeVars}}`);
+ });
+ });
+
+ describe('edge cases', () => {
+ it('should handle empty document', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('', 0);
+ const result = autocompletion(context);
+
+ expect(result).toBeNull();
+ });
+
+ it('should handle $ at the end of longer text', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://grafana.com?var=$', 25);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(25);
+ });
+
+ it('should handle = at the end of longer text', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://grafana.com?var=', 24);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(24);
+ });
+
+ it('should handle mixed content with ${ in the middle', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://grafana.com?var=${', 26);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should handle single suggestion', () => {
+ const singleSuggestion: VariableSuggestion[] = [
+ {
+ value: 'test',
+ label: 'test',
+ documentation: 'Test variable',
+ origin: VariableOrigin.Template,
+ },
+ ];
+ const autocompletion = dataLinkAutocompletion(singleSuggestion);
+ const context = createMockContext('$', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.options).toHaveLength(1);
+ expect(result.options[0].label).toBe('test');
+ });
+
+ it('should include all metadata fields in completion options', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('$', 1);
+ const result = autocompletion(context) as CompletionResult;
+
+ result.options.forEach((option) => {
+ expect(option).toHaveProperty('label');
+ expect(option).toHaveProperty('detail');
+ expect(option).toHaveProperty('info');
+ expect(option).toHaveProperty('apply');
+ expect(option).toHaveProperty('type');
+ expect(option.type).toBe('variable');
+ });
+ });
+ });
+
+ describe('completion context states', () => {
+ it('should handle explicit completion in middle of text', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('https://example.com', 10, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(10);
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should handle explicit completion at start of document', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 0, true);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(0);
+ expect(result.options).toHaveLength(4);
+ });
+
+ it('should not show completions for text without trigger when not explicit', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('test', 4);
+ const result = autocompletion(context);
+
+ expect(result).toBeNull();
+ });
+ });
+
+ describe('multiple variables in text', () => {
+ it('should handle completion after existing variable', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${myVar}$', 9);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(9);
+ });
+
+ it('should handle completion between variables', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('${var1}$${var2}', 8);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(8);
+ });
+
+ it('should handle completion in URL with multiple query params', () => {
+ const autocompletion = dataLinkAutocompletion(mockSuggestions);
+ const context = createMockContext('?a=${var1}&b=$', 14);
+ const result = autocompletion(context) as CompletionResult;
+
+ expect(result).not.toBeNull();
+ expect(result.from).toBe(14);
+ });
+ });
+});
diff --git a/packages/grafana-ui/src/components/DataLinks/codemirrorUtils.ts b/packages/grafana-ui/src/components/DataLinks/codemirrorUtils.ts
new file mode 100644
index 00000000000..16fc4d643f7
--- /dev/null
+++ b/packages/grafana-ui/src/components/DataLinks/codemirrorUtils.ts
@@ -0,0 +1,241 @@
+import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
+import { Extension } from '@codemirror/state';
+import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
+
+import { DataLinkBuiltInVars, GrafanaTheme2, VariableOrigin, VariableSuggestion } from '@grafana/data';
+
+/**
+ * Creates a CodeMirror theme for data link input based on Grafana's theme
+ */
+export function createDataLinkTheme(theme: GrafanaTheme2): Extension {
+ const isDark = theme.colors.mode === 'dark';
+
+ return EditorView.theme(
+ {
+ '&': {
+ fontSize: theme.typography.body.fontSize,
+ fontFamily: theme.typography.fontFamilyMonospace,
+ backgroundColor: 'transparent',
+ border: 'none',
+ outline: 'none',
+ },
+ '.cm-placeholder': {
+ color: theme.colors.text.disabled,
+ fontStyle: 'normal',
+ },
+ '.cm-scroller': {
+ overflow: 'auto',
+ fontFamily: theme.typography.fontFamilyMonospace,
+ },
+ '.cm-content': {
+ padding: '3px 0',
+ color: theme.colors.text.primary,
+ caretColor: theme.colors.text.primary,
+ },
+ '.cm-line': {
+ padding: '0 2px',
+ },
+ '.cm-cursor': {
+ borderLeftColor: theme.colors.text.primary,
+ },
+ '.cm-selectionBackground': {
+ backgroundColor: `${theme.colors.action.selected} !important`,
+ },
+ '&.cm-focused .cm-selectionBackground': {
+ backgroundColor: `${theme.colors.action.focus} !important`,
+ },
+ '.cm-variable': {
+ color: theme.colors.success.text,
+ fontWeight: theme.typography.fontWeightMedium,
+ },
+ '.cm-activeLine': {
+ backgroundColor: 'transparent',
+ },
+ '.cm-gutters': {
+ display: 'none',
+ },
+ '.cm-tooltip': {
+ zIndex: theme.zIndex.portal + 1, // Above modals and portals (1062)
+ },
+ '.cm-tooltip.cm-tooltip-autocomplete': {
+ backgroundColor: theme.colors.background.primary,
+ border: `1px solid ${theme.colors.border.weak}`,
+ boxShadow: theme.shadows.z3,
+ },
+ '.cm-tooltip.cm-tooltip-autocomplete > ul': {
+ fontFamily: theme.typography.fontFamily,
+ maxHeight: '300px',
+ },
+ '.cm-tooltip.cm-tooltip-autocomplete > ul > li': {
+ padding: '2px 8px',
+ color: theme.colors.text.primary,
+ },
+ '.cm-tooltip-autocomplete ul li[aria-selected]': {
+ backgroundColor: theme.colors.background.secondary,
+ color: theme.colors.text.primary,
+ },
+ '.cm-completionLabel': {
+ fontFamily: theme.typography.fontFamilyMonospace,
+ fontSize: theme.typography.size.sm,
+ },
+ '.cm-completionDetail': {
+ color: theme.colors.text.secondary,
+ fontStyle: 'normal',
+ marginLeft: theme.spacing(1),
+ },
+ '.cm-completionInfo': {
+ backgroundColor: theme.colors.background.primary,
+ border: `1px solid ${theme.colors.border.weak}`,
+ color: theme.colors.text.primary,
+ padding: theme.spacing(1),
+ },
+ },
+ { dark: isDark }
+ );
+}
+
+/**
+ * Creates a syntax highlighter for data link variables (${...})
+ * Matches the pattern from the old Prism implementation: (\${\S+?})
+ */
+export function createDataLinkHighlighter(theme: GrafanaTheme2): Extension {
+ // Regular expression matching ${...} patterns (same as old implementation)
+ const variablePattern = /\$\{[^}]+\}/g;
+
+ const variableDecoration = Decoration.mark({
+ class: 'cm-variable',
+ });
+
+ const viewPlugin = ViewPlugin.fromClass(
+ class {
+ decorations: DecorationSet;
+
+ constructor(view: EditorView) {
+ this.decorations = this.buildDecorations(view);
+ }
+
+ update(update: ViewUpdate) {
+ if (update.docChanged || update.viewportChanged) {
+ this.decorations = this.buildDecorations(update.view);
+ }
+ }
+
+ buildDecorations(view: EditorView): DecorationSet {
+ const decorations: Array<{ from: number; to: number }> = [];
+ const text = view.state.doc.toString();
+ let match;
+
+ // Reset regex state
+ variablePattern.lastIndex = 0;
+
+ while ((match = variablePattern.exec(text)) !== null) {
+ decorations.push({
+ from: match.index,
+ to: match.index + match[0].length,
+ });
+ }
+
+ return Decoration.set(decorations.map((range) => variableDecoration.range(range.from, range.to)));
+ }
+ },
+ {
+ decorations: (v) => v.decorations,
+ }
+ );
+
+ return viewPlugin;
+}
+
+/**
+ * Creates autocomplete function for data link variables
+ * Triggers on $ and = characters
+ */
+export function dataLinkAutocompletion(
+ suggestions: VariableSuggestion[]
+): (context: CompletionContext) => CompletionResult | null {
+ return (context: CompletionContext): CompletionResult | null => {
+ // Match $ or = followed by optional { and word characters
+ // This will match: $, ${, ${word, =, etc.
+ const word = context.matchBefore(/[$=]\{?[\w.]*$/);
+
+ // Don't show completions if there are no suggestions
+ if (suggestions.length === 0) {
+ return null;
+ }
+
+ // For explicit completion (Ctrl+Space), show at cursor position
+ if (context.explicit) {
+ const options: Completion[] = suggestions.map((suggestion) => {
+ let applyText: string;
+
+ if (suggestion.origin !== VariableOrigin.Template || suggestion.value === DataLinkBuiltInVars.includeVars) {
+ applyText = `\${${suggestion.value}}`;
+ } else {
+ applyText = `\${${suggestion.value}:queryparam}`;
+ }
+
+ return {
+ label: suggestion.label,
+ detail: suggestion.origin,
+ info: suggestion.documentation,
+ apply: applyText,
+ type: 'variable',
+ };
+ });
+
+ return {
+ from: context.pos,
+ options,
+ };
+ }
+
+ // If no match on typing, don't show completions
+ if (!word) {
+ return null;
+ }
+
+ // Check if the match starts with a trigger character
+ const triggerChar = word.text.charAt(0);
+ if (triggerChar !== '$' && triggerChar !== '=') {
+ return null;
+ }
+
+ // For single trigger character ($ or =), start from current position to show completions
+ // But the 'apply' text will still replace correctly
+ const isSingleChar = word.text.length === 1;
+
+ const options: Completion[] = suggestions.map((suggestion) => {
+ // Always insert the full variable syntax
+ let applyText: string;
+
+ if (suggestion.origin !== VariableOrigin.Template || suggestion.value === DataLinkBuiltInVars.includeVars) {
+ applyText = `\${${suggestion.value}}`;
+ } else {
+ applyText = `\${${suggestion.value}:queryparam}`;
+ }
+
+ return {
+ label: suggestion.label,
+ detail: suggestion.origin,
+ info: suggestion.documentation,
+ // Use a custom apply function to handle replacement properly
+ apply: isSingleChar
+ ? (view, completion, from, to) => {
+ // Replace from the trigger character position
+ let wordFrom = triggerChar === '=' ? context.pos : word.from;
+ view.dispatch({
+ changes: { from: wordFrom, to, insert: applyText },
+ selection: { anchor: wordFrom + applyText.length }, // Move cursor to end of inserted text
+ });
+ }
+ : applyText,
+ type: 'variable',
+ };
+ });
+
+ return {
+ from: isSingleChar ? context.pos : word.from,
+ options,
+ };
+ };
+}
diff --git a/yarn.lock b/yarn.lock
index 1b4710e4062..e8edad4f9cb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1572,6 +1572,65 @@ __metadata:
languageName: node
linkType: hard
+"@codemirror/autocomplete@npm:^6.12.0":
+ version: 6.20.0
+ resolution: "@codemirror/autocomplete@npm:6.20.0"
+ dependencies:
+ "@codemirror/language": "npm:^6.0.0"
+ "@codemirror/state": "npm:^6.0.0"
+ "@codemirror/view": "npm:^6.17.0"
+ "@lezer/common": "npm:^1.0.0"
+ checksum: 10/ba3603b860c30dd4f8b7c20085680d2f491022db95fe1f3aa6a58363c64678efb3ba795d715755c8a02121631317cf7fbe44cfa3b4cdb01ebca2b4ed36ea5d8a
+ languageName: node
+ linkType: hard
+
+"@codemirror/commands@npm:^6.3.3":
+ version: 6.10.1
+ resolution: "@codemirror/commands@npm:6.10.1"
+ dependencies:
+ "@codemirror/language": "npm:^6.0.0"
+ "@codemirror/state": "npm:^6.4.0"
+ "@codemirror/view": "npm:^6.27.0"
+ "@lezer/common": "npm:^1.1.0"
+ checksum: 10/9e305263dc457635fa1c7e5b47756958be5367e38f5bb07a3abfd5966591e2eafd57ea0c5c738b28bb3ab5de64c07a5302ebd49b129ff7e48b225841f66e647f
+ languageName: node
+ linkType: hard
+
+"@codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.10.0":
+ version: 6.12.1
+ resolution: "@codemirror/language@npm:6.12.1"
+ dependencies:
+ "@codemirror/state": "npm:^6.0.0"
+ "@codemirror/view": "npm:^6.23.0"
+ "@lezer/common": "npm:^1.5.0"
+ "@lezer/highlight": "npm:^1.0.0"
+ "@lezer/lr": "npm:^1.0.0"
+ style-mod: "npm:^4.0.0"
+ checksum: 10/a24c3512d38cbb2a20cc3128da0eea074b4a6102b6a5a041b3dfd5e67638fb61dcdf4743ed87708db882df5d72a84d9f891aac6fa68447830989c8e2d9ffa2ba
+ languageName: node
+ linkType: hard
+
+"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0":
+ version: 6.5.3
+ resolution: "@codemirror/state@npm:6.5.3"
+ dependencies:
+ "@marijn/find-cluster-break": "npm:^1.0.0"
+ checksum: 10/07dc8e06aa3c78bde36fd584d1e1131a529d244474dd36bffc6ad1033701d6628a02259711692d099b2a482ede015930f20106aa8ebc7b251db6f303bc72caa2
+ languageName: node
+ linkType: hard
+
+"@codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0":
+ version: 6.39.9
+ resolution: "@codemirror/view@npm:6.39.9"
+ dependencies:
+ "@codemirror/state": "npm:^6.5.0"
+ crelt: "npm:^1.0.6"
+ style-mod: "npm:^4.1.0"
+ w3c-keyname: "npm:^2.2.4"
+ checksum: 10/9e86b35f31fd4f8b4c2fe608fa6116ddc71261acd842c405de41de1f752268c47ea8e0c400818b4d0481a629e1f773dda9e6f0d24d38ed6a9f6b3d58b2dff669
+ languageName: node
+ linkType: hard
+
"@colors/colors@npm:1.5.0":
version: 1.5.0
resolution: "@colors/colors@npm:1.5.0"
@@ -3770,6 +3829,11 @@ __metadata:
resolution: "@grafana/ui@workspace:packages/grafana-ui"
dependencies:
"@babel/core": "npm:7.28.0"
+ "@codemirror/autocomplete": "npm:^6.12.0"
+ "@codemirror/commands": "npm:^6.3.3"
+ "@codemirror/language": "npm:^6.10.0"
+ "@codemirror/state": "npm:^6.4.0"
+ "@codemirror/view": "npm:^6.23.0"
"@emotion/css": "npm:11.13.5"
"@emotion/react": "npm:11.14.0"
"@emotion/serialize": "npm:1.3.3"
@@ -3781,6 +3845,7 @@ __metadata:
"@grafana/i18n": "npm:12.4.0-pre"
"@grafana/schema": "npm:12.4.0-pre"
"@hello-pangea/dnd": "npm:18.0.1"
+ "@lezer/highlight": "npm:^1.2.0"
"@monaco-editor/react": "npm:4.7.0"
"@popperjs/core": "npm:2.11.8"
"@rc-component/drawer": "npm:1.3.0"
@@ -5192,7 +5257,14 @@ __metadata:
languageName: node
linkType: hard
-"@lezer/highlight@npm:1.2.3":
+"@lezer/common@npm:^1.1.0, @lezer/common@npm:^1.5.0":
+ version: 1.5.0
+ resolution: "@lezer/common@npm:1.5.0"
+ checksum: 10/d99a45947c5033476f7c16f475b364e5b276e89a351641d8d785ceac88e8175f7b7b7d43dda80c3d9097f5e3379f018404bbe59a41d15992df23a03bbef3519b
+ languageName: node
+ linkType: hard
+
+"@lezer/highlight@npm:1.2.3, @lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.2.0":
version: 1.2.3
resolution: "@lezer/highlight@npm:1.2.3"
dependencies:
@@ -5210,6 +5282,15 @@ __metadata:
languageName: node
linkType: hard
+"@lezer/lr@npm:^1.0.0":
+ version: 1.4.7
+ resolution: "@lezer/lr@npm:1.4.7"
+ dependencies:
+ "@lezer/common": "npm:^1.0.0"
+ checksum: 10/5407e10c8f983eedd8eaace9f2582aac39f7b280cdcf4e396d53ca6c1e654ce1bb2fdbddfbf9a63c8462046be37c8c4da180be7ffaf2d2aa24eb71622f624d85
+ languageName: node
+ linkType: hard
+
"@linaria/core@npm:^4.5.4":
version: 4.5.4
resolution: "@linaria/core@npm:4.5.4"
@@ -5387,6 +5468,13 @@ __metadata:
languageName: node
linkType: hard
+"@marijn/find-cluster-break@npm:^1.0.0":
+ version: 1.0.2
+ resolution: "@marijn/find-cluster-break@npm:1.0.2"
+ checksum: 10/92fe7ba43ce3d3314f593e4c2fd822d7089649baff47a474fe04b83e3119931d7cf58388747d429ff65fa2db14f5ca57e787268c482e868fc67759511f61f09b
+ languageName: node
+ linkType: hard
+
"@mdx-js/react@npm:^3.0.0":
version: 3.0.1
resolution: "@mdx-js/react@npm:3.0.1"
@@ -14930,6 +15018,13 @@ __metadata:
languageName: node
linkType: hard
+"crelt@npm:^1.0.6":
+ version: 1.0.6
+ resolution: "crelt@npm:1.0.6"
+ checksum: 10/5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3
+ languageName: node
+ linkType: hard
+
"croact-css-styled@npm:^1.1.9":
version: 1.1.9
resolution: "croact-css-styled@npm:1.1.9"
@@ -31798,6 +31893,13 @@ __metadata:
languageName: node
linkType: hard
+"style-mod@npm:^4.0.0, style-mod@npm:^4.1.0":
+ version: 4.1.3
+ resolution: "style-mod@npm:4.1.3"
+ checksum: 10/b47465ea953c42e62682a2a366a0946a4aa973cbabb000619acbf5d1c162c94aa019caeb13804e38bed71c2b19b8c778f847542d7e82e9309154ccbb5ef9ca98
+ languageName: node
+ linkType: hard
+
"style-search@npm:^0.1.0":
version: 0.1.0
resolution: "style-search@npm:0.1.0"
@@ -33839,6 +33941,13 @@ __metadata:
languageName: node
linkType: hard
+"w3c-keyname@npm:^2.2.4":
+ version: 2.2.8
+ resolution: "w3c-keyname@npm:2.2.8"
+ checksum: 10/95bafa4c04fa2f685a86ca1000069c1ec43ace1f8776c10f226a73296caeddd83f893db885c2c220ebeb6c52d424e3b54d7c0c1e963bbf204038ff1a944fbb07
+ languageName: node
+ linkType: hard
+
"w3c-xmlserializer@npm:^3.0.0":
version: 3.0.0
resolution: "w3c-xmlserializer@npm:3.0.0"