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