mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
parent
df72344d3c
commit
bbd24cd93a
@ -1,36 +1,37 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withTheme } from '../../themes';
|
import { withTheme } from '../../themes';
|
||||||
import { Themeable } from '../../types';
|
import { Themeable } from '../../types';
|
||||||
import { KeyCode, editor, KeyMod } from 'monaco-editor/esm/vs/editor/editor.api';
|
import { CodeEditorProps } from './types';
|
||||||
|
import { registerSuggestions } from './suggestions';
|
||||||
import ReactMonaco from 'react-monaco-editor';
|
import ReactMonaco from 'react-monaco-editor';
|
||||||
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||||
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
|
|
||||||
*/
|
|
||||||
onEditorDidMount?: (editor: editor.IStandaloneCodeEditor) => void;
|
|
||||||
|
|
||||||
/** Handler to be performed when editor is blurred */
|
|
||||||
onBlur?: CodeEditorChangeHandler;
|
|
||||||
|
|
||||||
/** Handler to be performed when Cmd/Ctrl+S is pressed */
|
|
||||||
onSave?: CodeEditorChangeHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = CodeEditorProps & Themeable;
|
type Props = CodeEditorProps & Themeable;
|
||||||
|
|
||||||
class UnthemedCodeEditor extends React.PureComponent<Props> {
|
class UnthemedCodeEditor extends React.PureComponent<Props> {
|
||||||
|
completionCancel?: monaco.IDisposable;
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.completionCancel) {
|
||||||
|
console.log('dispose of the custom completion stuff');
|
||||||
|
this.completionCancel.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(oldProps: Props) {
|
||||||
|
const { getSuggestions, language } = this.props;
|
||||||
|
if (getSuggestions) {
|
||||||
|
// Language changed
|
||||||
|
if (language !== oldProps.language) {
|
||||||
|
if (this.completionCancel) {
|
||||||
|
this.completionCancel.dispose();
|
||||||
|
}
|
||||||
|
this.completionCancel = registerSuggestions(language, getSuggestions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is replaced with a real function when the actual editor mounts
|
||||||
getEditorValue = () => '';
|
getEditorValue = () => '';
|
||||||
|
|
||||||
onBlur = () => {
|
onBlur = () => {
|
||||||
@ -40,13 +41,20 @@ class UnthemedCodeEditor extends React.PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editorDidMount = (editor: editor.IStandaloneCodeEditor) => {
|
editorWillMount = (m: typeof monaco) => {
|
||||||
|
const { language, getSuggestions } = this.props;
|
||||||
|
if (getSuggestions) {
|
||||||
|
this.completionCancel = registerSuggestions(language, getSuggestions);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||||
const { onSave, onEditorDidMount } = this.props;
|
const { onSave, onEditorDidMount } = this.props;
|
||||||
|
|
||||||
this.getEditorValue = () => editor.getValue();
|
this.getEditorValue = () => editor.getValue();
|
||||||
|
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
editor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => {
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, () => {
|
||||||
onSave(this.getEditorValue());
|
onSave(this.getEditorValue());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -61,7 +69,7 @@ class UnthemedCodeEditor extends React.PureComponent<Props> {
|
|||||||
const value = this.props.value ?? '';
|
const value = this.props.value ?? '';
|
||||||
const longText = value.length > 100;
|
const longText = value.length > 100;
|
||||||
|
|
||||||
const options: editor.IEditorConstructionOptions = {
|
const options: monaco.editor.IEditorConstructionOptions = {
|
||||||
wordWrap: 'off',
|
wordWrap: 'off',
|
||||||
codeLens: false, // not included in the bundle
|
codeLens: false, // not included in the bundle
|
||||||
minimap: {
|
minimap: {
|
||||||
@ -91,6 +99,7 @@ class UnthemedCodeEditor extends React.PureComponent<Props> {
|
|||||||
theme={theme.isDark ? 'vs-dark' : 'vs-light'}
|
theme={theme.isDark ? 'vs-dark' : 'vs-light'}
|
||||||
value={value}
|
value={value}
|
||||||
options={options}
|
options={options}
|
||||||
|
editorWillMount={this.editorWillMount}
|
||||||
editorDidMount={this.editorDidMount}
|
editorDidMount={this.editorDidMount}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -98,5 +107,4 @@ class UnthemedCodeEditor extends React.PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CodeEditorChangeHandler = (value: string) => void;
|
|
||||||
export default withTheme(UnthemedCodeEditor);
|
export default withTheme(UnthemedCodeEditor);
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useAsyncDependency } from '../../utils/useAsyncDependency';
|
import { useAsyncDependency } from '../../utils/useAsyncDependency';
|
||||||
import { ErrorWithStack, LoadingPlaceholder } from '..';
|
import { ErrorWithStack, LoadingPlaceholder } from '..';
|
||||||
import { CodeEditorProps } from './CodeEditor';
|
import { CodeEditorProps } from './types';
|
||||||
|
|
||||||
export type CodeEditorChangeHandler = (value: string) => void;
|
|
||||||
|
|
||||||
export const CodeEditor: React.FC<CodeEditorProps> = props => {
|
export const CodeEditor: React.FC<CodeEditorProps> = props => {
|
||||||
const { loading, error, dependency } = useAsyncDependency(
|
const { loading, error, dependency } = useAsyncDependency(
|
||||||
@ -11,7 +9,7 @@ export const CodeEditor: React.FC<CodeEditorProps> = props => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <LoadingPlaceholder text={'Loading...'} />;
|
return <LoadingPlaceholder text={''} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
104
packages/grafana-ui/src/components/Monaco/suggestions.ts
Normal file
104
packages/grafana-ui/src/components/Monaco/suggestions.ts
Normal file
@ -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;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
77
packages/grafana-ui/src/components/Monaco/types.ts
Normal file
77
packages/grafana-ui/src/components/Monaco/types.ts
Normal file
@ -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;
|
||||||
|
}
|
17
packages/grafana-ui/src/components/Monaco/utils.ts
Normal file
17
packages/grafana-ui/src/components/Monaco/utils.ts
Normal file
@ -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,
|
||||||
|
};
|
||||||
|
}
|
@ -34,7 +34,11 @@ export { FilterPill } from './FilterPill/FilterPill';
|
|||||||
|
|
||||||
export { ConfirmModal } from './ConfirmModal/ConfirmModal';
|
export { ConfirmModal } from './ConfirmModal/ConfirmModal';
|
||||||
export { QueryField } from './QueryField/QueryField';
|
export { QueryField } from './QueryField/QueryField';
|
||||||
|
|
||||||
|
// Code editor
|
||||||
export { CodeEditor } from './Monaco/CodeEditorLazy';
|
export { CodeEditor } from './Monaco/CodeEditorLazy';
|
||||||
|
export { CodeEditorSuggestionItem, CodeEditorSuggestionItemKind } from './Monaco/types';
|
||||||
|
export { variableSuggestionToCodeEditorSuggestion } from './Monaco/utils';
|
||||||
|
|
||||||
// TODO: namespace
|
// TODO: namespace
|
||||||
export { Modal } from './Modal/Modal';
|
export { Modal } from './Modal/Modal';
|
||||||
|
@ -5,11 +5,13 @@ import {
|
|||||||
PanelOptionsEditorItem,
|
PanelOptionsEditorItem,
|
||||||
PanelPlugin,
|
PanelPlugin,
|
||||||
StandardEditorContext,
|
StandardEditorContext,
|
||||||
|
VariableSuggestionsScope,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { get as lodashGet, set as lodashSet } from 'lodash';
|
import { get as lodashGet, set as lodashSet } from 'lodash';
|
||||||
import { Field, Label } from '@grafana/ui';
|
import { Field, Label } from '@grafana/ui';
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import { OptionsGroup } from './OptionsGroup';
|
import { OptionsGroup } from './OptionsGroup';
|
||||||
|
import { getPanelOptionsVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
|
||||||
|
|
||||||
interface PanelOptionsEditorProps<TOptions> {
|
interface PanelOptionsEditorProps<TOptions> {
|
||||||
plugin: PanelPlugin;
|
plugin: PanelPlugin;
|
||||||
@ -38,9 +40,12 @@ export const PanelOptionsEditor: React.FC<PanelOptionsEditorProps<any>> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const context: StandardEditorContext<any> = {
|
const context: StandardEditorContext<any> = {
|
||||||
data: data ?? [],
|
data: data || [],
|
||||||
replaceVariables,
|
replaceVariables,
|
||||||
options,
|
options,
|
||||||
|
getSuggestions: (scope?: VariableSuggestionsScope) => {
|
||||||
|
return getPanelOptionsVariableSuggestions(plugin, data);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
urlUtil,
|
urlUtil,
|
||||||
textUtil,
|
textUtil,
|
||||||
DataLink,
|
DataLink,
|
||||||
|
PanelPlugin,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
|
||||||
const timeRangeVars = [
|
const timeRangeVars = [
|
||||||
@ -231,6 +232,18 @@ export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: Data
|
|||||||
return [...seriesVars, ...fieldVars, ...valueVars, valueCalcVar, ...getPanelLinksVariableSuggestions()];
|
return [...seriesVars, ...fieldVars, ...valueVars, valueCalcVar, ...getPanelLinksVariableSuggestions()];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getPanelOptionsVariableSuggestions = (plugin: PanelPlugin, data?: DataFrame[]): VariableSuggestion[] => {
|
||||||
|
const dataVariables = plugin.meta.skipDataQuery ? [] : getDataFrameVars(data || []);
|
||||||
|
return [
|
||||||
|
...dataVariables, // field values
|
||||||
|
...templateSrv.getVariables().map(variable => ({
|
||||||
|
value: variable.name as string,
|
||||||
|
label: variable.name,
|
||||||
|
origin: VariableOrigin.Template,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
export interface LinkService {
|
export interface LinkService {
|
||||||
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel<T>;
|
getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel<T>;
|
||||||
getAnchorInfo: (link: any) => any;
|
getAnchorInfo: (link: any) => any;
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { CodeEditor, stylesFactory, useTheme } from '@grafana/ui';
|
import {
|
||||||
|
CodeEditor,
|
||||||
|
stylesFactory,
|
||||||
|
useTheme,
|
||||||
|
CodeEditorSuggestionItem,
|
||||||
|
variableSuggestionToCodeEditorSuggestion,
|
||||||
|
} from '@grafana/ui';
|
||||||
import { GrafanaTheme, StandardEditorProps } from '@grafana/data';
|
import { GrafanaTheme, StandardEditorProps } from '@grafana/data';
|
||||||
|
|
||||||
import { TextOptions } from './types';
|
import { TextOptions } from './types';
|
||||||
@ -10,6 +16,14 @@ export const TextPanelEditor: FC<StandardEditorProps<string, any, TextOptions>>
|
|||||||
const language = useMemo(() => context.options?.mode ?? 'markdown', [context]);
|
const language = useMemo(() => context.options?.mode ?? 'markdown', [context]);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
|
|
||||||
|
const getSuggestions = (): CodeEditorSuggestionItem[] => {
|
||||||
|
if (!context.getSuggestions) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return context.getSuggestions().map(v => variableSuggestionToCodeEditorSuggestion(v));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.editorBox)}>
|
<div className={cx(styles.editorBox)}>
|
||||||
<AutoSizer disableHeight>
|
<AutoSizer disableHeight>
|
||||||
@ -17,7 +31,6 @@ export const TextPanelEditor: FC<StandardEditorProps<string, any, TextOptions>>
|
|||||||
if (width === 0) {
|
if (width === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={value}
|
value={value}
|
||||||
@ -28,6 +41,7 @@ export const TextPanelEditor: FC<StandardEditorProps<string, any, TextOptions>>
|
|||||||
showMiniMap={false}
|
showMiniMap={false}
|
||||||
showLineNumbers={false}
|
showLineNumbers={false}
|
||||||
height="200px"
|
height="200px"
|
||||||
|
getSuggestions={getSuggestions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -80,7 +80,7 @@ module.exports = {
|
|||||||
'!cursorUndo',
|
'!cursorUndo',
|
||||||
'!dnd',
|
'!dnd',
|
||||||
'!find',
|
'!find',
|
||||||
'!folding',
|
'folding',
|
||||||
'!fontZoom',
|
'!fontZoom',
|
||||||
'!format',
|
'!format',
|
||||||
'!gotoError',
|
'!gotoError',
|
||||||
@ -93,14 +93,14 @@ module.exports = {
|
|||||||
'!linesOperations',
|
'!linesOperations',
|
||||||
'!links',
|
'!links',
|
||||||
'!multicursor',
|
'!multicursor',
|
||||||
'!parameterHints',
|
'parameterHints',
|
||||||
'!quickCommand',
|
'!quickCommand',
|
||||||
'!quickOutline',
|
'!quickOutline',
|
||||||
'!referenceSearch',
|
'!referenceSearch',
|
||||||
'!rename',
|
'!rename',
|
||||||
'!smartSelect',
|
'!smartSelect',
|
||||||
'!snippets',
|
'!snippets',
|
||||||
'!suggest',
|
'suggest',
|
||||||
'!toggleHighContrast',
|
'!toggleHighContrast',
|
||||||
'!toggleTabFocusMode',
|
'!toggleTabFocusMode',
|
||||||
'!transpose',
|
'!transpose',
|
||||||
|
Loading…
Reference in New Issue
Block a user