mirror of https://github.com/grafana/grafana
Monaco: add suggestions for template variables (#25921)
* now with suggestions * using suggestions API * using variable suggestions * using variable suggestions * show variables * minor cleanup * add @alpha warning * Do not produce data variables if panel does not support queries Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>pull/25965/head
parent
df72344d3c
commit
bbd24cd93a
@ -0,0 +1,104 @@ |
|||||||
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; |
||||||
|
|
||||||
|
import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind, CodeEditorSuggestionProvider } from './types'; |
||||||
|
|
||||||
|
function getCompletionItems( |
||||||
|
prefix: string, |
||||||
|
suggestions: CodeEditorSuggestionItem[], |
||||||
|
range: monaco.IRange |
||||||
|
): monaco.languages.CompletionItem[] { |
||||||
|
const items: monaco.languages.CompletionItem[] = []; |
||||||
|
for (const suggestion of suggestions) { |
||||||
|
if (prefix && !suggestion.label.startsWith(prefix)) { |
||||||
|
continue; // skip non-matching suggestions
|
||||||
|
} |
||||||
|
|
||||||
|
items.push({ |
||||||
|
...suggestion, |
||||||
|
kind: mapKinds(suggestion.kind), |
||||||
|
range, |
||||||
|
insertText: suggestion.insertText ?? suggestion.label, |
||||||
|
}); |
||||||
|
} |
||||||
|
return items; |
||||||
|
} |
||||||
|
|
||||||
|
function mapKinds(sug?: CodeEditorSuggestionItemKind): monaco.languages.CompletionItemKind { |
||||||
|
switch (sug) { |
||||||
|
case CodeEditorSuggestionItemKind.Method: |
||||||
|
return monaco.languages.CompletionItemKind.Method; |
||||||
|
case CodeEditorSuggestionItemKind.Field: |
||||||
|
return monaco.languages.CompletionItemKind.Field; |
||||||
|
case CodeEditorSuggestionItemKind.Property: |
||||||
|
return monaco.languages.CompletionItemKind.Property; |
||||||
|
case CodeEditorSuggestionItemKind.Constant: |
||||||
|
return monaco.languages.CompletionItemKind.Constant; |
||||||
|
case CodeEditorSuggestionItemKind.Text: |
||||||
|
return monaco.languages.CompletionItemKind.Text; |
||||||
|
} |
||||||
|
return monaco.languages.CompletionItemKind.Text; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @alpha |
||||||
|
*/ |
||||||
|
export function registerSuggestions( |
||||||
|
language: string, |
||||||
|
getSuggestions: CodeEditorSuggestionProvider |
||||||
|
): monaco.IDisposable | undefined { |
||||||
|
if (!language || !getSuggestions) { |
||||||
|
return undefined; |
||||||
|
} |
||||||
|
return monaco.languages.registerCompletionItemProvider(language, { |
||||||
|
triggerCharacters: ['$'], |
||||||
|
|
||||||
|
provideCompletionItems: (model, position, context) => { |
||||||
|
if (context.triggerCharacter === '$') { |
||||||
|
const range = { |
||||||
|
startLineNumber: position.lineNumber, |
||||||
|
endLineNumber: position.lineNumber, |
||||||
|
startColumn: position.column - 1, |
||||||
|
endColumn: position.column, |
||||||
|
}; |
||||||
|
return { |
||||||
|
suggestions: getCompletionItems('$', getSuggestions(), range), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// find out if we are completing a property in the 'dependencies' object.
|
||||||
|
const lineText = model.getValueInRange({ |
||||||
|
startLineNumber: position.lineNumber, |
||||||
|
startColumn: 1, |
||||||
|
endLineNumber: position.lineNumber, |
||||||
|
endColumn: position.column, |
||||||
|
}); |
||||||
|
|
||||||
|
const idx = lineText.lastIndexOf('$'); |
||||||
|
if (idx >= 0) { |
||||||
|
const range = { |
||||||
|
startLineNumber: position.lineNumber, |
||||||
|
endLineNumber: position.lineNumber, |
||||||
|
startColumn: idx, // the last $ we found
|
||||||
|
endColumn: position.column, |
||||||
|
}; |
||||||
|
return { |
||||||
|
suggestions: getCompletionItems(lineText.substr(idx), getSuggestions(), range), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// Empty line that asked for suggestion
|
||||||
|
if (lineText.trim().length < 1) { |
||||||
|
return { |
||||||
|
suggestions: getCompletionItems('', getSuggestions(), { |
||||||
|
startLineNumber: position.lineNumber, |
||||||
|
endLineNumber: position.lineNumber, |
||||||
|
startColumn: position.column, |
||||||
|
endColumn: position.column, |
||||||
|
}), |
||||||
|
}; |
||||||
|
} |
||||||
|
// console.log('complete?', lineText, context);
|
||||||
|
return undefined; |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
export type CodeEditorChangeHandler = (value: string) => void; |
||||||
|
export type CodeEditorSuggestionProvider = () => CodeEditorSuggestionItem[]; |
||||||
|
|
||||||
|
export interface CodeEditorProps { |
||||||
|
value: string; |
||||||
|
language: string; |
||||||
|
width?: number | string; |
||||||
|
height?: number | string; |
||||||
|
|
||||||
|
readOnly?: boolean; |
||||||
|
showMiniMap?: boolean; |
||||||
|
showLineNumbers?: boolean; |
||||||
|
|
||||||
|
/** |
||||||
|
* Callback after the editor has mounted that gives you raw access to monaco |
||||||
|
* |
||||||
|
* @experimental - real type is: monaco.editor.IStandaloneCodeEditor |
||||||
|
*/ |
||||||
|
onEditorDidMount?: (editor: any) => void; |
||||||
|
|
||||||
|
/** Handler to be performed when editor is blurred */ |
||||||
|
onBlur?: CodeEditorChangeHandler; |
||||||
|
|
||||||
|
/** Handler to be performed when Cmd/Ctrl+S is pressed */ |
||||||
|
onSave?: CodeEditorChangeHandler; |
||||||
|
|
||||||
|
/** |
||||||
|
* Language agnostic suggestion completions -- typically for template variables |
||||||
|
*/ |
||||||
|
getSuggestions?: CodeEditorSuggestionProvider; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @alpha |
||||||
|
*/ |
||||||
|
export enum CodeEditorSuggestionItemKind { |
||||||
|
Method = 'method', |
||||||
|
Field = 'field', |
||||||
|
Property = 'property', |
||||||
|
Constant = 'constant', |
||||||
|
Text = 'text', |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @alpha |
||||||
|
*/ |
||||||
|
export interface CodeEditorSuggestionItem { |
||||||
|
/** |
||||||
|
* The label of this completion item. By default |
||||||
|
* this is also the text that is inserted when selecting |
||||||
|
* this completion. |
||||||
|
*/ |
||||||
|
label: string; |
||||||
|
|
||||||
|
/** |
||||||
|
* The kind of this completion item. An icon is chosen |
||||||
|
* by the editor based on the kind. |
||||||
|
*/ |
||||||
|
kind?: CodeEditorSuggestionItemKind; |
||||||
|
|
||||||
|
/** |
||||||
|
* A human-readable string with additional information |
||||||
|
* about this item, like type or symbol information. |
||||||
|
*/ |
||||||
|
detail?: string; |
||||||
|
|
||||||
|
/** |
||||||
|
* A human-readable string that represents a doc-comment. |
||||||
|
*/ |
||||||
|
documentation?: string; // | IMarkdownString;
|
||||||
|
|
||||||
|
/** |
||||||
|
* A string or snippet that should be inserted in a document when selecting |
||||||
|
* this completion. When `falsy` the `label` is used. |
||||||
|
*/ |
||||||
|
insertText?: string; |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
import { VariableSuggestion } from '@grafana/data'; |
||||||
|
import { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind } from './types'; |
||||||
|
|
||||||
|
/** |
||||||
|
* @alpha |
||||||
|
*/ |
||||||
|
export function variableSuggestionToCodeEditorSuggestion(sug: VariableSuggestion): CodeEditorSuggestionItem { |
||||||
|
const label = '${' + sug.value + '}'; |
||||||
|
const detail = sug.value === sug.label ? sug.origin : `${sug.label} / ${sug.origin}`; |
||||||
|
|
||||||
|
return { |
||||||
|
label, |
||||||
|
kind: CodeEditorSuggestionItemKind.Property, |
||||||
|
detail, |
||||||
|
documentation: sug.documentation, |
||||||
|
}; |
||||||
|
} |
||||||
Loading…
Reference in new issue