mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Consolidate <CodeEditor/>
(#170313)
## Summary Fix https://github.com/elastic/kibana/issues/159719 - Remove duplicate of code_editor code from `kibana_react` and apply recent changes to the version in `packages/` - Fix code_editor styles in `packages/` https://github.com/elastic/kibana/pull/170313#discussion_r1378839369 - Revert setting default height to 100px (as it breaks in some places) https://github.com/elastic/kibana/pull/170313#discussion_r1378838788 ### Risks Ideally we should smoke check the code editor in all the places, I checked bunch of them. As of special custom features, I tested: - The theme switch - The placeholder - The a11y hint - Fullscreen mode
This commit is contained in:
parent
30c859206c
commit
3249c1a116
84 changed files with 152 additions and 3259 deletions
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
|
@ -78,9 +78,7 @@ x-pack/test/cloud_integration/plugins/saml_provider @elastic/kibana-core
|
|||
x-pack/plugins/cloud_integrations/cloud_links @elastic/kibana-core
|
||||
x-pack/plugins/cloud @elastic/kibana-core
|
||||
x-pack/plugins/cloud_security_posture @elastic/kibana-cloud-security-posture
|
||||
packages/shared-ux/code_editor/impl @elastic/appex-sharedux
|
||||
packages/shared-ux/code_editor/mocks @elastic/appex-sharedux
|
||||
packages/shared-ux/code_editor/types @elastic/appex-sharedux
|
||||
packages/shared-ux/code_editor @elastic/appex-sharedux
|
||||
packages/kbn-coloring @elastic/kibana-visualizations
|
||||
packages/kbn-config @elastic/kibana-core
|
||||
packages/kbn-config-mocks @elastic/kibana-core
|
||||
|
|
|
@ -184,9 +184,7 @@
|
|||
"@kbn/cloud-links-plugin": "link:x-pack/plugins/cloud_integrations/cloud_links",
|
||||
"@kbn/cloud-plugin": "link:x-pack/plugins/cloud",
|
||||
"@kbn/cloud-security-posture-plugin": "link:x-pack/plugins/cloud_security_posture",
|
||||
"@kbn/code-editor": "link:packages/shared-ux/code_editor/impl",
|
||||
"@kbn/code-editor-mocks": "link:packages/shared-ux/code_editor/mocks",
|
||||
"@kbn/code-editor-types": "link:packages/shared-ux/code_editor/types",
|
||||
"@kbn/code-editor": "link:packages/shared-ux/code_editor",
|
||||
"@kbn/coloring": "link:packages/kbn-coloring",
|
||||
"@kbn/config": "link:packages/kbn-config",
|
||||
"@kbn/config-mocks": "link:packages/kbn-config-mocks",
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
exports[`<CodeEditor /> hint element should be tabable 1`] = `
|
||||
<div
|
||||
aria-label="Code Editor"
|
||||
class="kibanaCodeEditor__keyboardHint"
|
||||
data-test-subj="codeEditorHint"
|
||||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop).,false"
|
||||
data-test-subj="codeEditorHint codeEditorHint--active"
|
||||
id="1234"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
@ -135,7 +135,7 @@ exports[`<CodeEditor /> is rendered 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Press {key} to start editing."
|
||||
id="kibana-react.kibanaCodeEditor.startEditing"
|
||||
id="sharedUXPackages.codeEditor.startEditing"
|
||||
values={
|
||||
Object {
|
||||
"key": <strong>
|
||||
|
@ -148,7 +148,7 @@ exports[`<CodeEditor /> is rendered 1`] = `
|
|||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Press {key} to stop editing."
|
||||
id="kibana-react.kibanaCodeEditor.stopEditing"
|
||||
id="sharedUXPackages.codeEditor.stopEditing"
|
||||
values={
|
||||
Object {
|
||||
"key": <strong>
|
||||
|
@ -255,8 +255,28 @@ exports[`<CodeEditor /> is rendered 1`] = `
|
|||
>
|
||||
<div
|
||||
aria-label="Code Editor"
|
||||
className="kibanaCodeEditor__keyboardHint"
|
||||
data-test-subj="codeEditorHint"
|
||||
css={
|
||||
Array [
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "jym74u",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
&:focus {
|
||||
z-index: 6000;
|
||||
}
|
||||
",
|
||||
"toString": [Function],
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
data-test-subj="codeEditorHint codeEditorHint--active"
|
||||
id="1234"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
|
@ -9,10 +9,10 @@
|
|||
import React from 'react';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { CodeEditorStorybookMock, CodeEditorStorybookParams } from '@kbn/code-editor-mocks';
|
||||
import { monaco as monacoEditor } from '@kbn/monaco';
|
||||
|
||||
import { CodeEditorStorybookMock, CodeEditorStorybookParams } from './mocks/storybook';
|
||||
|
||||
import mdx from './README.mdx';
|
||||
|
||||
import { CodeEditor } from './code_editor';
|
|
@ -133,16 +133,20 @@ describe('<CodeEditor />', () => {
|
|||
|
||||
test('should be tabable', () => {
|
||||
const DOMnode = getHint().getDOMNode();
|
||||
expect(getHint().find('[data-test-subj="codeEditorHint"]').exists()).toBeTruthy();
|
||||
expect(getHint().find('[data-test-subj~="codeEditorHint"]').exists()).toBeTruthy();
|
||||
expect(DOMnode.getAttribute('tabindex')).toBe('0');
|
||||
expect(DOMnode).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should be disabled when the ui monaco editor gains focus', async () => {
|
||||
// Initially it is visible and active
|
||||
expect(getHint().find('[data-test-subj="codeEditorHint"]').exists()).toBeTruthy();
|
||||
expect(getHint().find('[data-test-subj~="codeEditorHint"]').prop('data-test-subj')).toContain(
|
||||
`codeEditorHint--active`
|
||||
);
|
||||
getHint().simulate('keydown', { key: keys.ENTER });
|
||||
expect(getHint().find('[data-test-subj="codeEditorHint"]').exists()).toBeFalsy();
|
||||
expect(getHint().find('[data-test-subj~="codeEditorHint"]').prop('data-test-subj')).toContain(
|
||||
`codeEditorHint--inactive`
|
||||
);
|
||||
});
|
||||
|
||||
test('should be enabled when hitting the ESC key', () => {
|
||||
|
@ -152,7 +156,9 @@ describe('<CodeEditor />', () => {
|
|||
keyCode: monaco.KeyCode.Escape,
|
||||
});
|
||||
|
||||
// expect((getHint().props() as any).className).not.toContain('isInactive');
|
||||
expect(getHint().find('[data-test-subj~="codeEditorHint"]').prop('data-test-subj')).toContain(
|
||||
`codeEditorHint--active`
|
||||
);
|
||||
});
|
||||
|
||||
test('should detect that the suggestion menu is open and not show the hint on ESC', async () => {
|
|
@ -25,23 +25,20 @@ import {
|
|||
import { monaco } from '@kbn/monaco';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
import './register_languages';
|
||||
import { remeasureFonts } from './remeasure_fonts';
|
||||
|
||||
import { PlaceholderWidget } from './placeholder_widget';
|
||||
import {
|
||||
codeEditorControlsStyles,
|
||||
codeEditorControlsWithinFullScreenStyles,
|
||||
codeEditorFullScreenStyles,
|
||||
codeEditorKeyboardHintStyles,
|
||||
codeEditorStyles,
|
||||
styles,
|
||||
DARK_THEME,
|
||||
LIGHT_THEME,
|
||||
DARK_THEME_TRANSPARENT,
|
||||
LIGHT_THEME_TRANSPARENT,
|
||||
} from './editor.styles';
|
||||
|
||||
export interface Props {
|
||||
export interface CodeEditorProps {
|
||||
/** Width of editor. Defaults to 100%. */
|
||||
width?: string | number;
|
||||
|
||||
|
@ -132,12 +129,12 @@ export interface Props {
|
|||
allowFullScreen?: boolean;
|
||||
}
|
||||
|
||||
export const CodeEditor: React.FC<Props> = ({
|
||||
export const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
languageId,
|
||||
value,
|
||||
onChange,
|
||||
width,
|
||||
height = '100px',
|
||||
height,
|
||||
options,
|
||||
overrideEditorWillMount,
|
||||
editorDidMount,
|
||||
|
@ -182,12 +179,6 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
const textboxMutationObserver = useRef<MutationObserver | null>(null);
|
||||
|
||||
const [isHintActive, setIsHintActive] = useState(true);
|
||||
const defaultStyles = codeEditorStyles();
|
||||
const hintStyles = codeEditorKeyboardHintStyles(euiTheme.levels);
|
||||
|
||||
const promptClasses = useMemo(() => {
|
||||
return isHintActive ? [defaultStyles, hintStyles] : [defaultStyles];
|
||||
}, [isHintActive, defaultStyles, hintStyles]);
|
||||
|
||||
const _updateDimensions = useCallback(() => {
|
||||
_editor.current?.layout();
|
||||
|
@ -269,14 +260,12 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
<p>
|
||||
{isReadOnly ? (
|
||||
<FormattedMessage
|
||||
css={defaultStyles}
|
||||
id="sharedUXPackages.codeEditor.startEditingReadOnly"
|
||||
defaultMessage="Press {key} to start interacting with the code."
|
||||
values={{ key: enterKey }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
css={defaultStyles}
|
||||
id="sharedUXPackages.codeEditor.startEditing"
|
||||
defaultMessage="Press {key} to start editing."
|
||||
values={{ key: enterKey }}
|
||||
|
@ -286,14 +275,12 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
<p>
|
||||
{isReadOnly ? (
|
||||
<FormattedMessage
|
||||
css={defaultStyles}
|
||||
id="sharedUXPackages.codeEditor.stopEditingReadOnly"
|
||||
defaultMessage="Press {key} to stop interacting with the code."
|
||||
values={{ key: escapeKey }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
css={defaultStyles}
|
||||
id="sharedUXPackages.codeEditor.stopEditing"
|
||||
defaultMessage="Press {key} to stop editing."
|
||||
values={{ key: escapeKey }}
|
||||
|
@ -304,7 +291,13 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
}
|
||||
>
|
||||
<div
|
||||
css={promptClasses}
|
||||
css={[
|
||||
styles.keyboardHint(euiTheme),
|
||||
!isHintActive &&
|
||||
css`
|
||||
display: none;
|
||||
`,
|
||||
]}
|
||||
id={htmlIdGenerator('codeEditor')()}
|
||||
ref={editorHint}
|
||||
tabIndex={0}
|
||||
|
@ -312,19 +305,11 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
onClick={startEditing}
|
||||
onKeyDown={onKeyDownHint}
|
||||
aria-label={ariaLabel}
|
||||
data-test-subj={isHintActive ? 'codeEditorHint' : 'codeEditor'}
|
||||
data-test-subj={`codeEditorHint codeEditorHint--${isHintActive ? 'active' : 'inactive'}`}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}, [
|
||||
onKeyDownHint,
|
||||
startEditing,
|
||||
ariaLabel,
|
||||
isReadOnly,
|
||||
promptClasses,
|
||||
defaultStyles,
|
||||
isHintActive,
|
||||
]);
|
||||
}, [isHintActive, isReadOnly, euiTheme, startEditing, onKeyDownHint, ariaLabel]);
|
||||
|
||||
const _editorWillMount = useCallback(
|
||||
(__monaco: unknown) => {
|
||||
|
@ -357,6 +342,7 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
}
|
||||
});
|
||||
|
||||
// Register themes
|
||||
monaco.editor.defineTheme('euiColors', useDarkTheme ? DARK_THEME : LIGHT_THEME);
|
||||
monaco.editor.defineTheme(
|
||||
'euiColorsTransparent',
|
||||
|
@ -433,35 +419,46 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
useEffect(() => {
|
||||
if (placeholder && !value && _editor.current) {
|
||||
// Mounts editor inside constructor
|
||||
_placeholderWidget.current = new PlaceholderWidget(placeholder, _editor.current);
|
||||
_placeholderWidget.current = new PlaceholderWidget(placeholder, euiTheme, _editor.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
_placeholderWidget.current?.dispose();
|
||||
_placeholderWidget.current = null;
|
||||
};
|
||||
}, [placeholder, value]);
|
||||
}, [placeholder, value, euiTheme]);
|
||||
|
||||
const { CopyButton } = useCopy({ isCopyable, value });
|
||||
|
||||
const controlStyles = useMemo(() => {
|
||||
const copyableStyles = [defaultStyles, codeEditorControlsStyles(euiTheme.size, euiTheme.base)];
|
||||
return allowFullScreen || isCopyable ? copyableStyles && defaultStyles : defaultStyles;
|
||||
}, [allowFullScreen, isCopyable, defaultStyles, euiTheme]);
|
||||
|
||||
const theme = useMemo(() => {
|
||||
// register theme for dark or light
|
||||
useEffect(() => {
|
||||
// Register themes when 'useDarkThem' changes
|
||||
monaco.editor.defineTheme('euiColors', useDarkTheme ? DARK_THEME : LIGHT_THEME);
|
||||
return options?.theme ?? (transparentBackground ? 'euiColorsTransparent' : 'euiColors');
|
||||
}, [useDarkTheme, transparentBackground, options]);
|
||||
monaco.editor.defineTheme(
|
||||
'euiColorsTransparent',
|
||||
useDarkTheme ? DARK_THEME_TRANSPARENT : LIGHT_THEME_TRANSPARENT
|
||||
);
|
||||
}, [useDarkTheme]);
|
||||
|
||||
const theme = options?.theme ?? (transparentBackground ? 'euiColorsTransparent' : 'euiColors');
|
||||
|
||||
return (
|
||||
<div css={codeEditorStyles()} onKeyDown={onKeyDown}>
|
||||
<div
|
||||
css={styles.container}
|
||||
onKeyDown={onKeyDown}
|
||||
data-test-subj="kibanaCodeEditor"
|
||||
className="kibanaCodeEditor"
|
||||
>
|
||||
{renderPrompt()}
|
||||
|
||||
<FullScreenDisplay>
|
||||
{allowFullScreen || isCopyable ? (
|
||||
<div css={controlStyles}>
|
||||
<div
|
||||
css={
|
||||
isFullScreen
|
||||
? [styles.controls.base(euiTheme), styles.controls.fullscreen(euiTheme)]
|
||||
: styles.controls.base(euiTheme)
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<CopyButton />
|
||||
|
@ -478,7 +475,6 @@ export const CodeEditor: React.FC<Props> = ({
|
|||
value={value}
|
||||
onChange={onChange}
|
||||
width={isFullScreen ? '100vw' : width}
|
||||
// previously defaulted to height which defaulted to 100% but this makes it unviewable
|
||||
height={isFullScreen ? '100vh' : height}
|
||||
editorWillMount={_editorWillMount}
|
||||
editorDidMount={_editorDidMount}
|
||||
|
@ -539,7 +535,6 @@ const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => {
|
|||
>
|
||||
{([fullscreenCollapse, fullscreenExpand]: string[]) => (
|
||||
<EuiButtonIcon
|
||||
css={[codeEditorStyles(), codeEditorFullScreenStyles]}
|
||||
onClick={toggleFullScreen}
|
||||
iconType={isFullScreen ? 'fullScreenExit' : 'fullScreen'}
|
||||
color="text"
|
||||
|
@ -551,8 +546,6 @@ const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const FullScreenDisplay = useMemo(
|
||||
() =>
|
||||
({ children }: { children: Array<JSX.Element | null> | JSX.Element }) => {
|
||||
|
@ -561,20 +554,12 @@ const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => {
|
|||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiFocusTrap clickOutsideDisables={true}>
|
||||
<div
|
||||
css={[
|
||||
codeEditorStyles(),
|
||||
codeEditorFullScreenStyles(),
|
||||
codeEditorControlsWithinFullScreenStyles(euiTheme.size.l),
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div css={styles.fullscreenContainer}>{children}</div>
|
||||
</EuiFocusTrap>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
},
|
||||
[isFullScreen, euiTheme]
|
||||
[isFullScreen]
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -593,7 +578,7 @@ const useCopy = ({ isCopyable, value }: { isCopyable: boolean; value: string })
|
|||
if (!showCopyButton) return null;
|
||||
|
||||
return (
|
||||
<div css={codeEditorStyles()} className="euiCodeBlock__copyButton">
|
||||
<div className="euiCodeBlock__copyButton">
|
||||
<EuiI18n token="euiCodeBlock.copyButton" default="Copy">
|
||||
{(copyButton: string) => (
|
||||
<EuiCopy textToCopy={value}>
|
|
@ -6,9 +6,44 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import type { EuiThemeComputed } from '@elastic/eui';
|
||||
import { euiDarkVars as darkTheme, euiLightVars as lightTheme } from '@kbn/ui-theme';
|
||||
|
||||
import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-theme';
|
||||
export const styles = {
|
||||
container: css`
|
||||
position: relative;
|
||||
height: 100%;
|
||||
`,
|
||||
fullscreenContainer: css`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
`,
|
||||
keyboardHint: (euiTheme: EuiThemeComputed) => css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
&:focus {
|
||||
z-index: ${euiTheme.levels.mask};
|
||||
}
|
||||
`,
|
||||
controls: {
|
||||
base: (euiTheme: EuiThemeComputed) => css`
|
||||
position: absolute;
|
||||
top: ${euiTheme.size.xs};
|
||||
right: ${euiTheme.size.base};
|
||||
z-index: ${euiTheme.levels.menu};
|
||||
`,
|
||||
fullscreen: (euiTheme: EuiThemeComputed) => css`
|
||||
top: ${euiTheme.size.l};
|
||||
right: ${euiTheme.size.l};
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
// NOTE: For talk around where this theme information will ultimately live,
|
||||
// please see this discuss issue: https://github.com/elastic/kibana/issues/43814
|
|
@ -1,396 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<CodeEditor /> hint element should be tabable 1`] = `
|
||||
<div
|
||||
aria-label="Code Editor"
|
||||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop).,You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
data-test-subj="codeEditorHint"
|
||||
id="1234"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`<CodeEditor /> is rendered 1`] = `
|
||||
<CodeEditor
|
||||
height={250}
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": Symbol(react.fragment),
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
languageId="loglang"
|
||||
onChange={[Function]}
|
||||
value="
|
||||
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
|
||||
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
|
||||
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
|
||||
"
|
||||
>
|
||||
<div
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "1dubd8m",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<EuiToolTip
|
||||
content={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "1dubd8m",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
defaultMessage="Press {key} to start editing."
|
||||
id="sharedUXPackages.codeEditor.startEditing"
|
||||
values={
|
||||
Object {
|
||||
"key": <strong>
|
||||
Enter
|
||||
</strong>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
css={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "1dubd8m",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
defaultMessage="Press {key} to stop editing."
|
||||
id="sharedUXPackages.codeEditor.stopEditing"
|
||||
values={
|
||||
Object {
|
||||
"key": <strong>
|
||||
Esc
|
||||
</strong>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
delay="regular"
|
||||
display="block"
|
||||
position="top"
|
||||
>
|
||||
<EuiToolTipAnchor
|
||||
display="block"
|
||||
id="generated-id"
|
||||
isVisible={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<span
|
||||
css="unknown styles"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<Insertion
|
||||
cache={
|
||||
Object {
|
||||
"insert": [Function],
|
||||
"inserted": Object {
|
||||
"uuw4g3-euiToolTipAnchor-block": true,
|
||||
},
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"registered": Object {},
|
||||
"sheet": StyleSheet {
|
||||
"_alreadyInsertedOrderInsensitiveRule": true,
|
||||
"_insertTag": [Function],
|
||||
"before": null,
|
||||
"container": <head>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.emotion-euiToolTipAnchor-block{display:block;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.emotion-euiToolTipAnchor-block *[disabled]{pointer-events:none;}
|
||||
</style>
|
||||
<style
|
||||
data-styled="active"
|
||||
data-styled-version="5.1.0"
|
||||
/>
|
||||
</head>,
|
||||
"ctr": 2,
|
||||
"insertionPoint": undefined,
|
||||
"isSpeedy": false,
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"prepend": undefined,
|
||||
"tags": Array [
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.emotion-euiToolTipAnchor-block{display:block;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.emotion-euiToolTipAnchor-block *[disabled]{pointer-events:none;}
|
||||
</style>,
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
isStringTag={true}
|
||||
serialized={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "uuw4g3-euiToolTipAnchor-block",
|
||||
"next": undefined,
|
||||
"styles": "*[disabled]{pointer-events:none;};label:euiToolTipAnchor;;;display:block;label:block;;;",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className="euiToolTipAnchor emotion-euiToolTipAnchor-block"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<div
|
||||
aria-label="Code Editor"
|
||||
css={
|
||||
Array [
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "1dubd8m",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
",
|
||||
"toString": [Function],
|
||||
},
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "7fzoim",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
&:focus {
|
||||
z-index: 6000;
|
||||
}
|
||||
|
||||
&--isInactive {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
",
|
||||
"toString": [Function],
|
||||
},
|
||||
]
|
||||
}
|
||||
data-test-subj="codeEditorHint"
|
||||
id="1234"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</EuiToolTipAnchor>
|
||||
</EuiToolTip>
|
||||
<Component>
|
||||
<mockMonacoEditor
|
||||
editorDidMount={[Function]}
|
||||
editorWillMount={[Function]}
|
||||
height={250}
|
||||
language="loglang"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Object {
|
||||
"fontFamily": "Roboto Mono",
|
||||
"fontSize": 12,
|
||||
"lineHeight": 21,
|
||||
"matchBrackets": "never",
|
||||
"minimap": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
"padding": Object {},
|
||||
"renderLineHighlight": "none",
|
||||
"scrollBeyondLastLine": false,
|
||||
"scrollbar": Object {
|
||||
"alwaysConsumeMouseWheel": false,
|
||||
"useShadows": false,
|
||||
},
|
||||
"wordBasedSuggestions": false,
|
||||
"wordWrap": "on",
|
||||
"wrappingIndent": "indent",
|
||||
}
|
||||
}
|
||||
theme="euiColors"
|
||||
value="
|
||||
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
|
||||
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
|
||||
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div />
|
||||
<textarea
|
||||
data-test-subj="monacoEditorTextarea"
|
||||
onKeyDown={[Function]}
|
||||
/>
|
||||
</div>
|
||||
</mockMonacoEditor>
|
||||
</Component>
|
||||
</div>
|
||||
</CodeEditor>
|
||||
`;
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ComponentSelector, css, CSSObject, SerializedStyles } from '@emotion/react';
|
||||
import { ArrayCSSInterpolation } from '@emotion/serialize';
|
||||
import { Property } from 'csstype';
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-theme';
|
||||
|
||||
export const codeEditorMonacoStyles = () => css`
|
||||
{
|
||||
animation: none !important; // Removes textarea EUI blue underline animation from EUI
|
||||
}
|
||||
`;
|
||||
|
||||
export const codeEditorStyles = () => css`
|
||||
{
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const codeEditorPlaceholderContainerStyles = (subduedText: string) => css`
|
||||
{
|
||||
color: ${subduedText};
|
||||
width: max-content;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const codeEditorKeyboardHintStyles = (levels: {
|
||||
content: Property.ZIndex;
|
||||
mask: Property.ZIndex;
|
||||
toast: Property.ZIndex;
|
||||
modal: Property.ZIndex;
|
||||
navigation: Property.ZIndex;
|
||||
menu: Property.ZIndex;
|
||||
header: Property.ZIndex;
|
||||
flyout: Property.ZIndex;
|
||||
maskBelowHeader: Property.ZIndex;
|
||||
}) =>
|
||||
css`
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
&:focus {
|
||||
z-index: ${levels.mask};
|
||||
}
|
||||
|
||||
&--isInactive {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const codeEditorControlsStyles = (
|
||||
size: {
|
||||
base: string;
|
||||
xxs: string;
|
||||
xs: string;
|
||||
s: string;
|
||||
m: string;
|
||||
l: string;
|
||||
xl: string;
|
||||
xxl: string;
|
||||
xxxl: string;
|
||||
xxxxl: string;
|
||||
},
|
||||
base:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| ComponentSelector
|
||||
| SerializedStyles
|
||||
| CSSObject
|
||||
| ArrayCSSInterpolation
|
||||
| null
|
||||
| undefined
|
||||
) => css`
|
||||
{
|
||||
top: ${size.xs};
|
||||
right: ${base};
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
`;
|
||||
|
||||
export const codeEditorFullScreenStyles = () => css`
|
||||
{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const codeEditorControlsWithinFullScreenStyles = (size: string) => css`
|
||||
top: ${size};
|
||||
right: ${size};
|
||||
}`;
|
||||
|
||||
// NOTE: For talk around where this theme information will ultimately live,
|
||||
// please see this discuss issue: https://github.com/elastic/kibana/issues/43814
|
||||
|
||||
export function createTheme(
|
||||
euiTheme: typeof darkTheme | typeof lightTheme,
|
||||
selectionBackgroundColor: string,
|
||||
backgroundColor?: string
|
||||
): monaco.editor.IStandaloneThemeData {
|
||||
return {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
token: '',
|
||||
foreground: euiTheme.euiColorDarkestShade,
|
||||
background: euiTheme.euiFormBackgroundColor,
|
||||
},
|
||||
{ token: 'invalid', foreground: euiTheme.euiColorAccent },
|
||||
{ token: 'emphasis', fontStyle: 'italic' },
|
||||
{ token: 'strong', fontStyle: 'bold' },
|
||||
|
||||
{ token: 'variable', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'variable.predefined', foreground: euiTheme.euiColorSuccess },
|
||||
{ token: 'constant', foreground: euiTheme.euiColorAccent },
|
||||
{ token: 'comment', foreground: euiTheme.euiColorMediumShade },
|
||||
{ token: 'number', foreground: euiTheme.euiColorAccent },
|
||||
{ token: 'number.hex', foreground: euiTheme.euiColorAccent },
|
||||
{ token: 'regexp', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'annotation', foreground: euiTheme.euiColorMediumShade },
|
||||
{ token: 'type', foreground: euiTheme.euiColorVis0 },
|
||||
|
||||
{ token: 'delimiter', foreground: euiTheme.euiTextSubduedColor },
|
||||
{ token: 'delimiter.html', foreground: euiTheme.euiColorDarkShade },
|
||||
{ token: 'delimiter.xml', foreground: euiTheme.euiColorPrimary },
|
||||
|
||||
{ token: 'tag', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'tag.id.jade', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'tag.class.jade', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'meta.scss', foreground: euiTheme.euiColorAccent },
|
||||
{ token: 'metatag', foreground: euiTheme.euiColorSuccess },
|
||||
{ token: 'metatag.content.html', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'metatag.html', foreground: euiTheme.euiColorMediumShade },
|
||||
{ token: 'metatag.xml', foreground: euiTheme.euiColorMediumShade },
|
||||
{ token: 'metatag.php', fontStyle: 'bold' },
|
||||
|
||||
{ token: 'key', foreground: euiTheme.euiColorWarning },
|
||||
{ token: 'string.key.json', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'string.value.json', foreground: euiTheme.euiColorPrimary },
|
||||
|
||||
{ token: 'attribute.name', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'attribute.name.css', foreground: euiTheme.euiColorSuccess },
|
||||
{ token: 'attribute.value', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'attribute.value.number', foreground: euiTheme.euiColorWarning },
|
||||
{ token: 'attribute.value.unit', foreground: euiTheme.euiColorWarning },
|
||||
{ token: 'attribute.value.html', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'attribute.value.xml', foreground: euiTheme.euiColorPrimary },
|
||||
|
||||
{ token: 'string', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'string.html', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'string.sql', foreground: euiTheme.euiColorDanger },
|
||||
{ token: 'string.yaml', foreground: euiTheme.euiColorPrimary },
|
||||
|
||||
{ token: 'keyword', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'keyword.json', foreground: euiTheme.euiColorPrimary },
|
||||
{ token: 'keyword.flow', foreground: euiTheme.euiColorWarning },
|
||||
{ token: 'keyword.flow.scss', foreground: euiTheme.euiColorPrimary },
|
||||
// Monaco editor supports strikethrough font style only starting from 0.32.0.
|
||||
{ token: 'keyword.deprecated', foreground: euiTheme.euiColorAccent },
|
||||
|
||||
{ token: 'operator.scss', foreground: euiTheme.euiColorDarkShade },
|
||||
{ token: 'operator.sql', foreground: euiTheme.euiColorMediumShade },
|
||||
{ token: 'operator.swift', foreground: euiTheme.euiColorMediumShade },
|
||||
{ token: 'predefined.sql', foreground: euiTheme.euiColorMediumShade },
|
||||
|
||||
{ token: 'text', foreground: euiTheme.euiTitleColor },
|
||||
{ token: 'label', foreground: euiTheme.euiColorVis9 },
|
||||
],
|
||||
colors: {
|
||||
'editor.foreground': euiTheme.euiColorDarkestShade,
|
||||
'editor.background': backgroundColor ?? euiTheme.euiFormBackgroundColor,
|
||||
'editorLineNumber.foreground': euiTheme.euiColorDarkShade,
|
||||
'editorLineNumber.activeForeground': euiTheme.euiColorDarkShade,
|
||||
'editorIndentGuide.background': euiTheme.euiColorLightShade,
|
||||
'editor.selectionBackground': selectionBackgroundColor,
|
||||
'editorWidget.border': euiTheme.euiColorLightShade,
|
||||
'editorWidget.background': euiTheme.euiColorLightestShade,
|
||||
'editorCursor.foreground': euiTheme.euiColorDarkestShade,
|
||||
'editorSuggestWidget.selectedBackground': euiTheme.euiColorLightShade,
|
||||
'list.hoverBackground': euiTheme.euiColorLightShade,
|
||||
'list.highlightForeground': euiTheme.euiColorPrimary,
|
||||
'editor.lineHighlightBorder': euiTheme.euiColorLightestShade,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const DARK_THEME = createTheme(darkTheme, '#343551');
|
||||
const LIGHT_THEME = createTheme(lightTheme, '#E3E4ED');
|
||||
const DARK_THEME_TRANSPARENT = createTheme(darkTheme, '#343551', '#00000000');
|
||||
const LIGHT_THEME_TRANSPARENT = createTheme(lightTheme, '#E3E4ED', '#00000000');
|
||||
|
||||
export { DARK_THEME, LIGHT_THEME, DARK_THEME_TRANSPARENT, LIGHT_THEME_TRANSPARENT };
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { LangModuleType } from '@kbn/monaco';
|
||||
import { lexerRules, languageConfiguration } from './language';
|
||||
|
||||
export const Lang: LangModuleType = { ID: 'css', lexerRules, languageConfiguration };
|
|
@ -1,184 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
|
||||
wordPattern: /(#?-?\d*\.\d\w*%?)|((::|[@#.!:])?[\w-?]+%?)|::|[@#.!:]/g,
|
||||
comments: {
|
||||
blockComment: ['/*', '*/'],
|
||||
},
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')'],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}', notIn: ['string', 'comment'] },
|
||||
{ open: '[', close: ']', notIn: ['string', 'comment'] },
|
||||
{ open: '(', close: ')', notIn: ['string', 'comment'] },
|
||||
{ open: '"', close: '"', notIn: ['string', 'comment'] },
|
||||
{ open: "'", close: "'", notIn: ['string', 'comment'] },
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
folding: {
|
||||
markers: {
|
||||
start: new RegExp('^\\s*\\/\\*\\s*#region\\b\\s*(.*?)\\s*\\*\\/'),
|
||||
end: new RegExp('^\\s*\\/\\*\\s*#endregion\\b.*\\*\\/'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const lexerRules: monaco.languages.IMonarchLanguage = {
|
||||
defaultToken: '',
|
||||
tokenPostfix: '.css',
|
||||
ws: '[ \t\n\r\f]*',
|
||||
identifier:
|
||||
'-?-?([a-zA-Z]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))([\\w\\-]|(\\\\(([0-9a-fA-F]{1,6}\\s?)|[^[0-9a-fA-F])))*',
|
||||
brackets: [
|
||||
{ open: '{', close: '}', token: 'delimiter.bracket' },
|
||||
{ open: '[', close: ']', token: 'delimiter.bracket' },
|
||||
{ open: '(', close: ')', token: 'delimiter.parenthesis' },
|
||||
{ open: '<', close: '>', token: 'delimiter.angle' },
|
||||
],
|
||||
tokenizer: {
|
||||
root: [{ include: '@selector' }],
|
||||
selector: [
|
||||
{ include: '@comments' },
|
||||
{ include: '@import' },
|
||||
{ include: '@strings' },
|
||||
[
|
||||
'[@](keyframes|-webkit-keyframes|-moz-keyframes|-o-keyframes)',
|
||||
{ token: 'keyword', next: '@keyframedeclaration' },
|
||||
],
|
||||
['[@](page|content|font-face|-moz-document)', { token: 'keyword' }],
|
||||
['[@](charset|namespace)', { token: 'keyword', next: '@declarationbody' }],
|
||||
[
|
||||
'(url-prefix)(\\()',
|
||||
['attribute.value', { token: 'delimiter.parenthesis', next: '@urldeclaration' }],
|
||||
],
|
||||
[
|
||||
'(url)(\\()',
|
||||
['attribute.value', { token: 'delimiter.parenthesis', next: '@urldeclaration' }],
|
||||
],
|
||||
{ include: '@selectorname' },
|
||||
['[\\*]', 'tag'],
|
||||
['[>\\+,]', 'delimiter'],
|
||||
['\\[', { token: 'delimiter.bracket', next: '@selectorattribute' }],
|
||||
['{', { token: 'delimiter.bracket', next: '@selectorbody' }],
|
||||
],
|
||||
selectorbody: [
|
||||
{ include: '@comments' },
|
||||
['[*_]?@identifier@ws:(?=(\\s|\\d|[^{;}]*[;}]))', 'attribute.name', '@rulevalue'],
|
||||
['}', { token: 'delimiter.bracket', next: '@pop' }],
|
||||
],
|
||||
selectorname: [['(\\.|#(?=[^{])|%|(@identifier)|:)+', 'tag']],
|
||||
selectorattribute: [{ include: '@term' }, [']', { token: 'delimiter.bracket', next: '@pop' }]],
|
||||
term: [
|
||||
{ include: '@comments' },
|
||||
[
|
||||
'(url-prefix)(\\()',
|
||||
['attribute.value', { token: 'delimiter.parenthesis', next: '@urldeclaration' }],
|
||||
],
|
||||
[
|
||||
'(url)(\\()',
|
||||
['attribute.value', { token: 'delimiter.parenthesis', next: '@urldeclaration' }],
|
||||
],
|
||||
{ include: '@functioninvocation' },
|
||||
{ include: '@numbers' },
|
||||
{ include: '@name' },
|
||||
['([<>=\\+\\-\\*\\/\\^\\|\\~,])', 'delimiter'],
|
||||
[',', 'delimiter'],
|
||||
],
|
||||
rulevalue: [
|
||||
{ include: '@comments' },
|
||||
{ include: '@strings' },
|
||||
{ include: '@term' },
|
||||
['!important', 'keyword'],
|
||||
[';', 'delimiter', '@pop'],
|
||||
['(?=})', { token: '', next: '@pop' }], // missing semicolon
|
||||
],
|
||||
warndebug: [['[@](warn|debug)', { token: 'keyword', next: '@declarationbody' }]],
|
||||
import: [['[@](import)', { token: 'keyword', next: '@declarationbody' }]],
|
||||
urldeclaration: [
|
||||
{ include: '@strings' },
|
||||
['[^)\r\n]+', 'string'],
|
||||
['\\)', { token: 'delimiter.parenthesis', next: '@pop' }],
|
||||
],
|
||||
parenthizedterm: [
|
||||
{ include: '@term' },
|
||||
['\\)', { token: 'delimiter.parenthesis', next: '@pop' }],
|
||||
],
|
||||
declarationbody: [
|
||||
{ include: '@term' },
|
||||
[';', 'delimiter', '@pop'],
|
||||
['(?=})', { token: '', next: '@pop' }], // missing semicolon
|
||||
],
|
||||
comments: [
|
||||
['\\/\\*', 'comment', '@comment'],
|
||||
['\\/\\/+.*', 'comment'],
|
||||
],
|
||||
comment: [
|
||||
['\\*\\/', 'comment', '@pop'],
|
||||
[/[^*/]+/, 'comment'],
|
||||
[/./, 'comment'],
|
||||
],
|
||||
name: [['@identifier', 'attribute.value']],
|
||||
numbers: [
|
||||
['-?(\\d*\\.)?\\d+([eE][\\-+]?\\d+)?', { token: 'attribute.value.number', next: '@units' }],
|
||||
['#[0-9a-fA-F_]+(?!\\w)', 'attribute.value.hex'],
|
||||
],
|
||||
units: [
|
||||
[
|
||||
'(em|ex|ch|rem|vmin|vmax|vw|vh|vm|cm|mm|in|px|pt|pc|deg|grad|rad|turn|s|ms|Hz|kHz|%)?',
|
||||
'attribute.value.unit',
|
||||
'@pop',
|
||||
],
|
||||
],
|
||||
keyframedeclaration: [
|
||||
['@identifier', 'attribute.value'],
|
||||
['{', { token: 'delimiter.bracket', switchTo: '@keyframebody' }],
|
||||
],
|
||||
keyframebody: [
|
||||
{ include: '@term' },
|
||||
['{', { token: 'delimiter.bracket', next: '@selectorbody' }],
|
||||
['}', { token: 'delimiter.bracket', next: '@pop' }],
|
||||
],
|
||||
functioninvocation: [
|
||||
['@identifier\\(', { token: 'attribute.value', next: '@functionarguments' }],
|
||||
],
|
||||
functionarguments: [
|
||||
['\\$@identifier@ws:', 'attribute.name'],
|
||||
['[,]', 'delimiter'],
|
||||
{ include: '@term' },
|
||||
['\\)', { token: 'attribute.value', next: '@pop' }],
|
||||
],
|
||||
strings: [
|
||||
['~?"', { token: 'string', next: '@stringenddoublequote' }],
|
||||
["~?'", { token: 'string', next: '@stringendquote' }],
|
||||
],
|
||||
stringenddoublequote: [
|
||||
['\\\\.', 'string'],
|
||||
['"', { token: 'string', next: '@pop' }],
|
||||
[/[^\\"]+/, 'string'],
|
||||
['.', 'string'],
|
||||
],
|
||||
stringendquote: [
|
||||
['\\\\.', 'string'],
|
||||
["'", { token: 'string', next: '@pop' }],
|
||||
[/[^\\']+/, 'string'],
|
||||
['.', 'string'],
|
||||
],
|
||||
},
|
||||
} as monaco.languages.IMonarchLanguage;
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LangModuleType } from '@kbn/monaco';
|
||||
import { languageConfiguration, lexerRules } from './language';
|
||||
|
||||
export const Lang: LangModuleType = { ID: 'handlebars', languageConfiguration, lexerRules };
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { LangModuleType } from '@kbn/monaco';
|
||||
import { languageConfiguration, lexerRules } from './language';
|
||||
|
||||
export const Lang: LangModuleType = { ID: 'hjson', languageConfiguration, lexerRules };
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Lang as CssLang } from './css';
|
||||
import { Lang as HandlebarsLang } from './handlebars';
|
||||
import { Lang as MarkdownLang } from './markdown';
|
||||
import { Lang as YamlLang } from './yaml';
|
||||
import { Lang as HJson } from './hjson';
|
||||
|
||||
export { CssLang, HandlebarsLang, MarkdownLang, YamlLang, HJson };
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { LangModuleType } from '@kbn/monaco';
|
||||
import { languageConfiguration, lexerRules } from './language';
|
||||
|
||||
export const Lang: LangModuleType = { ID: 'markdown', languageConfiguration, lexerRules };
|
|
@ -1,223 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is adapted from: https://code-room.io/libs/monaco-editor/esm/vs/basic-languages/markdown/markdown.js
|
||||
* License: https://github.com/microsoft/monaco-languages/blob/master/LICENSE.md
|
||||
*/
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
|
||||
comments: {
|
||||
blockComment: ['<!--', '-->'],
|
||||
},
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')'],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '<', close: '>', notIn: ['string'] },
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '`', close: '`' },
|
||||
],
|
||||
folding: {
|
||||
markers: {
|
||||
start: new RegExp('^\\s*<!--\\s*#?region\\b.*-->'),
|
||||
end: new RegExp('^\\s*<!--\\s*#?endregion\\b.*-->'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const lexerRules: monaco.languages.IMonarchLanguage = {
|
||||
defaultToken: '',
|
||||
tokenPostfix: '.md',
|
||||
// escape codes
|
||||
control: /[\\`*_\[\]{}()#+\-\.!]/,
|
||||
noncontrol: /[^\\`*_\[\]{}()#+\-\.!]/,
|
||||
escapes: /\\(?:@control)/,
|
||||
// escape codes for javascript/CSS strings
|
||||
jsescapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,
|
||||
// non matched elements
|
||||
empty: [
|
||||
'area',
|
||||
'base',
|
||||
'basefont',
|
||||
'br',
|
||||
'col',
|
||||
'frame',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'isindex',
|
||||
'link',
|
||||
'meta',
|
||||
'param',
|
||||
],
|
||||
tokenizer: {
|
||||
root: [
|
||||
// markdown tables
|
||||
[/^\s*\|/, '@rematch', '@table_header'],
|
||||
// headers (with #)
|
||||
[/^(\s{0,3})(#+)((?:[^\\#]|@escapes)+)((?:#+)?)/, ['white', 'keyword', 'keyword', 'keyword']],
|
||||
// headers (with =)
|
||||
[/^\s*(=+|\-+)\s*$/, 'keyword'],
|
||||
// headers (with ***)
|
||||
[/^\s*((\*[ ]?)+)\s*$/, 'meta.separator'],
|
||||
// quote
|
||||
[/^\s*>+/, 'comment'],
|
||||
// list (starting with * or number)
|
||||
[/^\s*([\*\-+:]|\d+\.)\s/, 'keyword'],
|
||||
// code block (4 spaces indent)
|
||||
[/^(\t|[ ]{4})[^ ].*$/, 'string'],
|
||||
// code block (3 tilde)
|
||||
[/^\s*~~~\s*((?:\w|[\/\-#])+)?\s*$/, { token: 'string', next: '@codeblock' }],
|
||||
// github style code blocks (with backticks and language)
|
||||
[
|
||||
/^\s*```\s*((?:\w|[\/\-#])+).*$/,
|
||||
{ token: 'string', next: '@codeblockgh', nextEmbedded: '$1' },
|
||||
],
|
||||
// github style code blocks (with backticks but no language)
|
||||
[/^\s*```\s*$/, { token: 'string', next: '@codeblock' }],
|
||||
// markup within lines
|
||||
{ include: '@linecontent' },
|
||||
],
|
||||
table_header: [{ include: '@table_common' }, [/[^\|]+/, 'keyword.table.header']],
|
||||
table_body: [{ include: '@table_common' }, { include: '@linecontent' }],
|
||||
table_common: [
|
||||
[/\s*[\-:]+\s*/, { token: 'keyword', switchTo: 'table_body' }],
|
||||
[/^\s*\|/, 'keyword.table.left'],
|
||||
[/^\s*[^\|]/, '@rematch', '@pop'],
|
||||
[/^\s*$/, '@rematch', '@pop'],
|
||||
[
|
||||
/\|/,
|
||||
{
|
||||
cases: {
|
||||
'@eos': 'keyword.table.right',
|
||||
'@default': 'keyword.table.middle',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
codeblock: [
|
||||
[/^\s*~~~\s*$/, { token: 'string', next: '@pop' }],
|
||||
[/^\s*```\s*$/, { token: 'string', next: '@pop' }],
|
||||
[/.*$/, 'variable.source'],
|
||||
],
|
||||
// github style code blocks
|
||||
codeblockgh: [
|
||||
[/```\s*$/, { token: 'variable.source', next: '@pop', nextEmbedded: '@pop' }],
|
||||
[/[^`]+/, 'variable.source'],
|
||||
],
|
||||
linecontent: [
|
||||
// escapes
|
||||
[/&\w+;/, 'string.escape'],
|
||||
[/@escapes/, 'escape'],
|
||||
// various markup
|
||||
[/\b__([^\\_]|@escapes|_(?!_))+__\b/, 'strong'],
|
||||
[/\*\*([^\\*]|@escapes|\*(?!\*))+\*\*/, 'strong'],
|
||||
[/\b_[^_]+_\b/, 'emphasis'],
|
||||
[/\*([^\\*]|@escapes)+\*/, 'emphasis'],
|
||||
[/`([^\\`]|@escapes)+`/, 'variable'],
|
||||
// links
|
||||
[/\{+[^}]+\}+/, 'string.target'],
|
||||
[/(!?\[)((?:[^\]\\]|@escapes)*)(\]\([^\)]+\))/, ['string.link', '', 'string.link']],
|
||||
[/(!?\[)((?:[^\]\\]|@escapes)*)(\])/, 'string.link'],
|
||||
// or html
|
||||
{ include: 'html' },
|
||||
],
|
||||
// Note: it is tempting to rather switch to the real HTML mode instead of building our own here
|
||||
// but currently there is a limitation in Monarch that prevents us from doing it: The opening
|
||||
// '<' would start the HTML mode, however there is no way to jump 1 character back to let the
|
||||
// HTML mode also tokenize the opening angle bracket. Thus, even though we could jump to HTML,
|
||||
// we cannot correctly tokenize it in that mode yet.
|
||||
html: [
|
||||
// html tags
|
||||
[/<(\w+)\/>/, 'tag'],
|
||||
[
|
||||
/<(\w+)/,
|
||||
{
|
||||
cases: {
|
||||
'@empty': { token: 'tag', next: '@tag.$1' },
|
||||
'@default': { token: 'tag', next: '@tag.$1' },
|
||||
},
|
||||
},
|
||||
],
|
||||
[/<\/(\w+)\s*>/, { token: 'tag' }],
|
||||
[/<!--/, 'comment', '@comment'],
|
||||
],
|
||||
comment: [
|
||||
[/[^<\-]+/, 'comment.content'],
|
||||
[/-->/, 'comment', '@pop'],
|
||||
[/<!--/, 'comment.content.invalid'],
|
||||
[/[<\-]/, 'comment.content'],
|
||||
],
|
||||
// Almost full HTML tag matching, complete with embedded scripts & styles
|
||||
tag: [
|
||||
[/[ \t\r\n]+/, 'white'],
|
||||
[
|
||||
/(type)(\s*=\s*)(")([^"]+)(")/,
|
||||
[
|
||||
'attribute.name.html',
|
||||
'delimiter.html',
|
||||
'string.html',
|
||||
{ token: 'string.html', switchTo: '@tag.$S2.$4' },
|
||||
'string.html',
|
||||
],
|
||||
],
|
||||
[
|
||||
/(type)(\s*=\s*)(')([^']+)(')/,
|
||||
[
|
||||
'attribute.name.html',
|
||||
'delimiter.html',
|
||||
'string.html',
|
||||
{ token: 'string.html', switchTo: '@tag.$S2.$4' },
|
||||
'string.html',
|
||||
],
|
||||
],
|
||||
[/(\w+)(\s*=\s*)("[^"]*"|'[^']*')/, ['attribute.name.html', 'delimiter.html', 'string.html']],
|
||||
[/\w+/, 'attribute.name.html'],
|
||||
[/\/>/, 'tag', '@pop'],
|
||||
[
|
||||
/>/,
|
||||
{
|
||||
cases: {
|
||||
'$S2==style': { token: 'tag', switchTo: 'embeddedStyle', nextEmbedded: 'text/css' },
|
||||
'$S2==script': {
|
||||
cases: {
|
||||
$S3: { token: 'tag', switchTo: 'embeddedScript', nextEmbedded: '$S3' },
|
||||
'@default': {
|
||||
token: 'tag',
|
||||
switchTo: 'embeddedScript',
|
||||
nextEmbedded: 'text/javascript',
|
||||
},
|
||||
},
|
||||
},
|
||||
'@default': { token: 'tag', next: '@pop' },
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
embeddedStyle: [
|
||||
[/[^<]+/, ''],
|
||||
[/<\/style\s*>/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }],
|
||||
[/</, ''],
|
||||
],
|
||||
embeddedScript: [
|
||||
[/[^<]+/, ''],
|
||||
[/<\/script\s*>/, { token: '@rematch', next: '@pop', nextEmbedded: '@pop' }],
|
||||
[/</, ''],
|
||||
],
|
||||
},
|
||||
} as monaco.languages.IMonarchLanguage;
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { LangModuleType } from '@kbn/monaco';
|
||||
import { languageConfiguration, lexerRules } from './language';
|
||||
|
||||
export const Lang: LangModuleType = { ID: 'yaml', languageConfiguration, lexerRules };
|
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
|
||||
comments: {
|
||||
lineComment: '#',
|
||||
},
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')'],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
surroundingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
folding: {
|
||||
offSide: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const lexerRules: monaco.languages.IMonarchLanguage = {
|
||||
tokenPostfix: '.yaml',
|
||||
brackets: [
|
||||
{ token: 'delimiter.bracket', open: '{', close: '}' },
|
||||
{ token: 'delimiter.square', open: '[', close: ']' },
|
||||
],
|
||||
keywords: ['true', 'True', 'TRUE', 'false', 'False', 'FALSE', 'null', 'Null', 'Null', '~'],
|
||||
numberInteger: /(?:0|[+-]?[0-9]+)/,
|
||||
numberFloat: /(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,
|
||||
numberOctal: /0o[0-7]+/,
|
||||
numberHex: /0x[0-9a-fA-F]+/,
|
||||
numberInfinity: /[+-]?\.(?:inf|Inf|INF)/,
|
||||
numberNaN: /\.(?:nan|Nan|NAN)/,
|
||||
numberDate: /\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,
|
||||
escapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,
|
||||
tokenizer: {
|
||||
root: [
|
||||
{ include: '@whitespace' },
|
||||
{ include: '@comment' },
|
||||
// Directive
|
||||
[/%[^ ]+.*$/, 'meta.directive'],
|
||||
// Document Markers
|
||||
[/---/, 'operators.directivesEnd'],
|
||||
[/\.{3}/, 'operators.documentEnd'],
|
||||
// Block Structure Indicators
|
||||
[/[-?:](?= )/, 'operators'],
|
||||
{ include: '@anchor' },
|
||||
{ include: '@tagHandle' },
|
||||
{ include: '@flowCollections' },
|
||||
{ include: '@blockStyle' },
|
||||
// Numbers
|
||||
[/@numberInteger(?![ \t]*\S+)/, 'number'],
|
||||
[/@numberFloat(?![ \t]*\S+)/, 'number.float'],
|
||||
[/@numberOctal(?![ \t]*\S+)/, 'number.octal'],
|
||||
[/@numberHex(?![ \t]*\S+)/, 'number.hex'],
|
||||
[/@numberInfinity(?![ \t]*\S+)/, 'number.infinity'],
|
||||
[/@numberNaN(?![ \t]*\S+)/, 'number.nan'],
|
||||
[/@numberDate(?![ \t]*\S+)/, 'number.date'],
|
||||
// Key:Value pair
|
||||
[/(".*?"|'.*?'|.*?)([ \t]*)(:)( |$)/, ['type', 'white', 'operators', 'white']],
|
||||
{ include: '@flowScalars' },
|
||||
// String nodes
|
||||
[
|
||||
/.+$/,
|
||||
{
|
||||
cases: {
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
// Flow Collection: Flow Mapping
|
||||
object: [
|
||||
{ include: '@whitespace' },
|
||||
{ include: '@comment' },
|
||||
// Flow Mapping termination
|
||||
[/\}/, '@brackets', '@pop'],
|
||||
// Flow Mapping delimiter
|
||||
[/,/, 'delimiter.comma'],
|
||||
// Flow Mapping Key:Value delimiter
|
||||
[/:(?= )/, 'operators'],
|
||||
// Flow Mapping Key:Value key
|
||||
[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/, 'type'],
|
||||
// Start Flow Style
|
||||
{ include: '@flowCollections' },
|
||||
{ include: '@flowScalars' },
|
||||
// Scalar Data types
|
||||
{ include: '@tagHandle' },
|
||||
{ include: '@anchor' },
|
||||
{ include: '@flowNumber' },
|
||||
// Other value (keyword or string)
|
||||
[
|
||||
/[^\},]+/,
|
||||
{
|
||||
cases: {
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
// Flow Collection: Flow Sequence
|
||||
array: [
|
||||
{ include: '@whitespace' },
|
||||
{ include: '@comment' },
|
||||
// Flow Sequence termination
|
||||
[/\]/, '@brackets', '@pop'],
|
||||
// Flow Sequence delimiter
|
||||
[/,/, 'delimiter.comma'],
|
||||
// Start Flow Style
|
||||
{ include: '@flowCollections' },
|
||||
{ include: '@flowScalars' },
|
||||
// Scalar Data types
|
||||
{ include: '@tagHandle' },
|
||||
{ include: '@anchor' },
|
||||
{ include: '@flowNumber' },
|
||||
// Other value (keyword or string)
|
||||
[
|
||||
/[^\],]+/,
|
||||
{
|
||||
cases: {
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
// First line of a Block Style
|
||||
multiString: [[/^( +).+$/, 'string', '@multiStringContinued.$1']],
|
||||
// Further lines of a Block Style
|
||||
// Workaround for indentation detection
|
||||
multiStringContinued: [
|
||||
[
|
||||
/^( *).+$/,
|
||||
{
|
||||
cases: {
|
||||
'$1==$S2': 'string',
|
||||
'@default': { token: '@rematch', next: '@popall' },
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
whitespace: [[/[ \t\r\n]+/, 'white']],
|
||||
// Only line comments
|
||||
comment: [[/#.*$/, 'comment']],
|
||||
// Start Flow Collections
|
||||
flowCollections: [
|
||||
[/\[/, '@brackets', '@array'],
|
||||
[/\{/, '@brackets', '@object'],
|
||||
],
|
||||
// Start Flow Scalars (quoted strings)
|
||||
flowScalars: [
|
||||
[/"([^"\\]|\\.)*$/, 'string.invalid'],
|
||||
[/'([^'\\]|\\.)*$/, 'string.invalid'],
|
||||
[/'[^']*'/, 'string'],
|
||||
[/"/, 'string', '@doubleQuotedString'],
|
||||
],
|
||||
doubleQuotedString: [
|
||||
[/[^\\"]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/"/, 'string', '@pop'],
|
||||
],
|
||||
// Start Block Scalar
|
||||
blockStyle: [[/[>|][0-9]*[+-]?$/, 'operators', '@multiString']],
|
||||
// Numbers in Flow Collections (terminate with ,]})
|
||||
flowNumber: [
|
||||
[/@numberInteger(?=[ \t]*[,\]\}])/, 'number'],
|
||||
[/@numberFloat(?=[ \t]*[,\]\}])/, 'number.float'],
|
||||
[/@numberOctal(?=[ \t]*[,\]\}])/, 'number.octal'],
|
||||
[/@numberHex(?=[ \t]*[,\]\}])/, 'number.hex'],
|
||||
[/@numberInfinity(?=[ \t]*[,\]\}])/, 'number.infinity'],
|
||||
[/@numberNaN(?=[ \t]*[,\]\}])/, 'number.nan'],
|
||||
[/@numberDate(?=[ \t]*[,\]\}])/, 'number.date'],
|
||||
],
|
||||
tagHandle: [[/\![^ ]*/, 'tag']],
|
||||
anchor: [[/[&*][^ ]+/, 'namespace']],
|
||||
},
|
||||
} as monaco.languages.IMonarchLanguage;
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { registerLanguage } from '@kbn/monaco';
|
||||
import { CssLang, HandlebarsLang, MarkdownLang, YamlLang, HJson } from './languages';
|
||||
|
||||
registerLanguage(CssLang);
|
||||
registerLanguage(HandlebarsLang);
|
||||
registerLanguage(MarkdownLang);
|
||||
registerLanguage(YamlLang);
|
||||
registerLanguage(HJson);
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { CodeEditor } from './code_editor';
|
||||
export { CodeEditor, type CodeEditorProps } from './code_editor';
|
|
@ -8,6 +8,6 @@
|
|||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../..',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/packages/shared-ux/code_editor'],
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
# @kbn/code-editor-mocks
|
||||
|
||||
Empty package generated by @kbn/generate
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
export { CodeEditorStorybookMock } from './storybook';
|
||||
export type { Params as CodeEditorStorybookParams } from './storybook';
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/packages/shared-ux/code_editor/mocks'],
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/code-editor-mocks",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "@kbn/code-editor-mocks",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -6,8 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock';
|
||||
|
||||
import type { Props as CodeEditorProps } from '@kbn/code-editor-types';
|
||||
import type { CodeEditorProps } from '../code_editor';
|
||||
|
||||
type PropArguments = Pick<
|
||||
CodeEditorProps,
|
||||
|
@ -19,7 +18,7 @@ type PropArguments = Pick<
|
|||
| 'placeholder'
|
||||
>;
|
||||
|
||||
export type Params = Record<keyof PropArguments, any>;
|
||||
export type CodeEditorStorybookParams = Record<keyof PropArguments, any>;
|
||||
|
||||
/**
|
||||
* Storybook mock for the `CodeEditor` component
|
||||
|
@ -74,7 +73,7 @@ export class CodeEditorStorybookMock extends AbstractStorybookMock<
|
|||
serviceArguments = {};
|
||||
dependencies = [];
|
||||
|
||||
getProps(params?: Params): CodeEditorProps {
|
||||
getProps(params?: CodeEditorStorybookParams): CodeEditorProps {
|
||||
return {
|
||||
languageId: this.getArgumentValue('languageId', params),
|
||||
value: this.getArgumentValue('value', params),
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/shared-ux-storybook-mock",
|
||||
"@kbn/code-editor-types",
|
||||
]
|
||||
}
|
|
@ -7,10 +7,13 @@
|
|||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import { css } from '@emotion/css';
|
||||
import type { EuiThemeComputed } from '@elastic/eui';
|
||||
|
||||
export class PlaceholderWidget implements monaco.editor.IContentWidget {
|
||||
constructor(
|
||||
private readonly placeholderText: string,
|
||||
private readonly euiTheme: EuiThemeComputed,
|
||||
private readonly editor: monaco.editor.ICodeEditor
|
||||
) {
|
||||
editor.addContentWidget(this);
|
||||
|
@ -26,7 +29,11 @@ export class PlaceholderWidget implements monaco.editor.IContentWidget {
|
|||
if (!this.domNode) {
|
||||
const domNode = document.createElement('div');
|
||||
domNode.innerText = this.placeholderText;
|
||||
domNode.className = 'kibanaCodeEditor__placeholderContainer';
|
||||
domNode.className = css`
|
||||
color: ${this.euiTheme.colors.subduedText};
|
||||
width: max-content;
|
||||
pointer-events: none;
|
||||
`;
|
||||
this.editor.applyFontInfo(domNode);
|
||||
this.domNode = domNode;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
|
@ -8,12 +8,12 @@
|
|||
"react",
|
||||
"@emotion/react/types/css-prop",
|
||||
"@kbn/ambient-ui-types",
|
||||
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"../../../typings/**/*",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
@ -22,8 +22,8 @@
|
|||
"@kbn/monaco",
|
||||
"@kbn/i18n",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/code-editor-mocks",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/shared-ux-storybook-mock",
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# @kbn/code-editor-types
|
||||
|
||||
Empty package generated by @kbn/generate
|
101
packages/shared-ux/code_editor/types/index.d.ts
vendored
101
packages/shared-ux/code_editor/types/index.d.ts
vendored
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export interface Props {
|
||||
/** Width of editor. Defaults to 100%. */
|
||||
width?: string | number;
|
||||
|
||||
/** Height of editor. Defaults to 100%. */
|
||||
height?: string | number;
|
||||
|
||||
/** ID of the editor language */
|
||||
languageId: enum;
|
||||
|
||||
/** Value of the editor */
|
||||
value: string;
|
||||
|
||||
/** Function invoked when text in editor is changed */
|
||||
onChange?: (value: string, event: monaco.editor.IModelContentChangedEvent) => void;
|
||||
|
||||
/**
|
||||
* Options for the Monaco Code Editor
|
||||
* Documentation of options can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
|
||||
*/
|
||||
options?: monaco.editor.IStandaloneEditorConstructionOptions;
|
||||
|
||||
/**
|
||||
* Suggestion provider for autocompletion
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.completionitemprovider.html
|
||||
*/
|
||||
suggestionProvider?: monaco.languages.CompletionItemProvider;
|
||||
|
||||
/**
|
||||
* Signature provider for function parameter info
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.signaturehelpprovider.html
|
||||
*/
|
||||
signatureProvider?: monaco.languages.SignatureHelpProvider;
|
||||
|
||||
/**
|
||||
* Hover provider for hover documentation
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.hoverprovider.html
|
||||
*/
|
||||
hoverProvider?: monaco.languages.HoverProvider;
|
||||
|
||||
/**
|
||||
* Language config provider for bracket
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.languageconfiguration.html
|
||||
*/
|
||||
languageConfiguration?: monaco.languages.LanguageConfiguration;
|
||||
|
||||
/**
|
||||
* Function called before the editor is mounted in the view
|
||||
*/
|
||||
editorWillMount?: () => void;
|
||||
/**
|
||||
* Function called before the editor is mounted in the view
|
||||
* and completely replaces the setup behavior called by the component
|
||||
*/
|
||||
overrideEditorWillMount?: () => void;
|
||||
|
||||
/**
|
||||
* Function called after the editor is mounted in the view
|
||||
*/
|
||||
editorDidMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void;
|
||||
/**
|
||||
* Should the editor use the dark theme
|
||||
*/
|
||||
useDarkTheme?: boolean;
|
||||
|
||||
/**
|
||||
* Should the editor use a transparent background
|
||||
*/
|
||||
transparentBackground?: boolean;
|
||||
|
||||
/**
|
||||
* Should the editor be rendered using the fullWidth EUI attribute
|
||||
*/
|
||||
fullWidth?: boolean;
|
||||
|
||||
/**
|
||||
* Place holder text for the code editor
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* Accessible name for the editor. (Defaults to "Code editor")
|
||||
*/
|
||||
'aria-label'?: string;
|
||||
|
||||
isCopyable?: boolean;
|
||||
allowFullScreen?: boolean;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/packages/shared-ux/code_editor/types'],
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/code-editor-types",
|
||||
"owner": "@elastic/appex-sharedux"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "@kbn/code-editor-types",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.d.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/monaco",
|
||||
]
|
||||
}
|
|
@ -3,14 +3,10 @@
|
|||
This re-usable code editor component was built as a layer of abstraction on top of the [Monaco Code Editor](https://microsoft.github.io/monaco-editor/) (and the [React Monaco Editor component](https://github.com/react-monaco-editor/react-monaco-editor)). The goal of this component is to expose a set of the most-used, most-helpful features from Monaco in a way that's easy to use out of the box. If a use case requires additional features, this component still allows access to all other Monaco features.
|
||||
|
||||
This editor component allows easy access to:
|
||||
* [Syntax highlighting (including custom language highlighting)](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages)
|
||||
* [Suggestion/autocompletion widget](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example)
|
||||
* Function signature widget
|
||||
* [Hover widget](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-hover-provider-example)
|
||||
|
||||
- [Syntax highlighting (including custom language highlighting)](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages)
|
||||
- [Suggestion/autocompletion widget](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-completion-provider-example)
|
||||
- Function signature widget
|
||||
- [Hover widget](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-hover-provider-example)
|
||||
|
||||
The Monaco editor doesn't automatically resize the editor area on window or container resize so this component includes a [resize detector](https://github.com/maslianok/react-resize-detector) to cause the Monaco editor to re-layout and adjust its size when the window or container size changes
|
||||
|
||||
## Storybook Examples
|
||||
To run the `CodeEditor` Storybook, from the root kibana directory, run `yarn storybook kibana_react`
|
||||
|
||||
All stories for the component live in `code_editor.examples.tsx`
|
|
@ -1,276 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { monaco as monacoEditor } from '@kbn/monaco';
|
||||
import { CodeEditor } from './code_editor';
|
||||
|
||||
// A sample language definition with a few example tokens
|
||||
// Taken from https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages
|
||||
const simpleLogLang: monacoEditor.languages.IMonarchLanguage = {
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/\[error.*/, 'constant'],
|
||||
[/\[notice.*/, 'variable'],
|
||||
[/\[info.*/, 'string'],
|
||||
[/\[[a-zA-Z 0-9:]+\]/, 'tag'],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
monacoEditor.languages.register({ id: 'loglang' });
|
||||
monacoEditor.languages.setMonarchTokensProvider('loglang', simpleLogLang);
|
||||
|
||||
const logs = `[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
|
||||
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
|
||||
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
|
||||
`;
|
||||
|
||||
storiesOf('CodeEditor', module)
|
||||
.addParameters({
|
||||
info: {
|
||||
// CodeEditor has no PropTypes set so this table will show up
|
||||
// as blank. I'm just disabling it to reduce confusion
|
||||
propTablesExclude: [CodeEditor],
|
||||
},
|
||||
})
|
||||
.add(
|
||||
'default',
|
||||
() => (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="plaintext"
|
||||
height={250}
|
||||
value="Hello!"
|
||||
onChange={action('onChange')}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
info: {
|
||||
text: 'Plaintext Monaco Editor',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'dark mode',
|
||||
() => (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="plaintext"
|
||||
height={250}
|
||||
value="Hello!"
|
||||
onChange={action('onChange')}
|
||||
useDarkTheme={true}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
info: {
|
||||
text: 'The dark theme is automatically used when dark mode is enabled in Kibana',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'transparent background',
|
||||
() => (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="plaintext"
|
||||
height={250}
|
||||
value="Hello!"
|
||||
onChange={action('onChange')}
|
||||
transparentBackground
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
info: {
|
||||
text: 'Plaintext Monaco Editor',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'custom log language',
|
||||
() => (
|
||||
<div>
|
||||
<CodeEditor languageId="loglang" height={250} value={logs} onChange={action('onChange')} />
|
||||
</div>
|
||||
),
|
||||
{
|
||||
info: {
|
||||
text: 'Custom language example. Language definition taken from [here](https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages)',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'hide minimap',
|
||||
() => (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="loglang"
|
||||
height={250}
|
||||
value={logs}
|
||||
onChange={action('onChange')}
|
||||
options={{
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
info: {
|
||||
text: 'The minimap (on left side of editor) can be disabled to save space',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'suggestion provider',
|
||||
() => {
|
||||
const provideSuggestions = (
|
||||
model: monacoEditor.editor.ITextModel,
|
||||
position: monacoEditor.Position,
|
||||
context: monacoEditor.languages.CompletionContext
|
||||
) => {
|
||||
const wordRange = new monacoEditor.Range(
|
||||
position.lineNumber,
|
||||
position.column,
|
||||
position.lineNumber,
|
||||
position.column
|
||||
);
|
||||
|
||||
return {
|
||||
suggestions: [
|
||||
{
|
||||
label: 'Hello, World',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Variable,
|
||||
documentation: {
|
||||
value: '*Markdown* can be used in autocomplete help',
|
||||
isTrusted: true,
|
||||
},
|
||||
insertText: 'Hello, World',
|
||||
range: wordRange,
|
||||
},
|
||||
{
|
||||
label: 'You know, for search',
|
||||
kind: monacoEditor.languages.CompletionItemKind.Variable,
|
||||
documentation: { value: 'Thanks `Monaco`', isTrusted: true },
|
||||
insertText: 'You know, for search',
|
||||
range: wordRange,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="loglang"
|
||||
height={250}
|
||||
value={logs}
|
||||
onChange={action('onChange')}
|
||||
suggestionProvider={{
|
||||
triggerCharacters: ['.'],
|
||||
provideCompletionItems: provideSuggestions,
|
||||
}}
|
||||
options={{
|
||||
quickSuggestions: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
info: {
|
||||
text: 'Example suggestion provider is triggered by the `.` character',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'hover provider',
|
||||
() => {
|
||||
const provideHover = (
|
||||
model: monacoEditor.editor.ITextModel,
|
||||
position: monacoEditor.Position
|
||||
) => {
|
||||
const word = model.getWordAtPosition(position);
|
||||
|
||||
if (!word) {
|
||||
return {
|
||||
contents: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
value: `You're hovering over **${word.word}**`,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="loglang"
|
||||
height={250}
|
||||
value={logs}
|
||||
onChange={action('onChange')}
|
||||
hoverProvider={{
|
||||
provideHover,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{
|
||||
info: {
|
||||
text: 'Hover dialog example can be triggered by hovering over a word',
|
||||
},
|
||||
}
|
||||
)
|
||||
.add(
|
||||
'json support',
|
||||
() => (
|
||||
<div>
|
||||
<CodeEditor
|
||||
languageId="json"
|
||||
editorDidMount={(editor) => {
|
||||
monacoEditor.languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
validate: true,
|
||||
schemas: [
|
||||
{
|
||||
uri: editor.getModel()?.uri.toString() ?? '',
|
||||
fileMatch: ['*'],
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
version: {
|
||||
enum: ['v1', 'v2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}}
|
||||
height={250}
|
||||
value="{}"
|
||||
onChange={action('onChange')}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
info: { text: 'JSON language support' },
|
||||
}
|
||||
);
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { useEffect, KeyboardEventHandler } from 'react';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
function createEditorInstance() {
|
||||
const keyDownListeners: Array<(e?: unknown) => void> = [];
|
||||
const didShowListeners: Array<(e?: unknown) => void> = [];
|
||||
const didHideListeners: Array<(e?: unknown) => void> = [];
|
||||
let placeholderDiv: undefined | HTMLDivElement;
|
||||
let areSuggestionsVisible = false;
|
||||
|
||||
const editorInstance = {
|
||||
// Mock monaco editor API
|
||||
getContribution: jest.fn((id: string) => {
|
||||
if (id === 'editor.contrib.suggestController') {
|
||||
return {
|
||||
widget: {
|
||||
value: {
|
||||
onDidShow: jest.fn((listener) => {
|
||||
didShowListeners.push(listener);
|
||||
}),
|
||||
onDidHide: jest.fn((listener) => {
|
||||
didHideListeners.push(listener);
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}),
|
||||
focus: jest.fn(),
|
||||
onDidBlurEditorText: jest.fn(),
|
||||
onKeyDown: jest.fn((listener) => {
|
||||
keyDownListeners.push(listener);
|
||||
}),
|
||||
addContentWidget: jest.fn((widget: monaco.editor.IContentWidget) => {
|
||||
placeholderDiv?.appendChild(widget.getDomNode());
|
||||
}),
|
||||
applyFontInfo: jest.fn(),
|
||||
removeContentWidget: jest.fn((widget: monaco.editor.IContentWidget) => {
|
||||
placeholderDiv?.removeChild(widget.getDomNode());
|
||||
}),
|
||||
getDomNode: jest.fn(),
|
||||
// Helpers for our tests
|
||||
__helpers__: {
|
||||
areSuggestionsVisible: () => areSuggestionsVisible,
|
||||
getPlaceholderRef: (div: HTMLDivElement) => {
|
||||
placeholderDiv = div;
|
||||
},
|
||||
onTextareaKeyDown: ((e) => {
|
||||
// Let all our listener know that a key has been pressed on the textarea
|
||||
keyDownListeners.forEach((listener) => listener(e));
|
||||
|
||||
// Close the suggestions when hitting the ESC key
|
||||
if (e.keyCode === monaco.KeyCode.Escape && areSuggestionsVisible) {
|
||||
editorInstance.__helpers__.hideSuggestions();
|
||||
}
|
||||
}) as KeyboardEventHandler,
|
||||
showSuggestions: () => {
|
||||
areSuggestionsVisible = true;
|
||||
didShowListeners.forEach((listener) => listener());
|
||||
},
|
||||
hideSuggestions: () => {
|
||||
areSuggestionsVisible = false;
|
||||
didHideListeners.forEach((listener) => listener());
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return editorInstance;
|
||||
}
|
||||
|
||||
type MockedEditor = ReturnType<typeof createEditorInstance>;
|
||||
|
||||
export const mockedEditorInstance: MockedEditor = createEditorInstance();
|
||||
|
||||
// <MonacoEditor /> mock
|
||||
const mockMonacoEditor = ({
|
||||
editorWillMount,
|
||||
editorDidMount,
|
||||
}: Record<string, (...args: unknown[]) => void>) => {
|
||||
editorWillMount(monaco);
|
||||
|
||||
useEffect(() => {
|
||||
editorDidMount(mockedEditorInstance, monaco);
|
||||
}, [editorDidMount]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={mockedEditorInstance?.__helpers__.getPlaceholderRef} />
|
||||
<textarea
|
||||
onKeyDown={mockedEditorInstance?.__helpers__.onTextareaKeyDown}
|
||||
data-test-subj="monacoEditorTextarea"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
jest.mock('react-monaco-editor', () => {
|
||||
return function JestMockEditor() {
|
||||
return mockMonacoEditor;
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the htmlIdGenerator to generate predictable ids for snapshot tests
|
||||
jest.mock('@elastic/eui', () => {
|
||||
const original = jest.requireActual('@elastic/eui');
|
||||
|
||||
return {
|
||||
...original,
|
||||
htmlIdGenerator: () => {
|
||||
return () => '1234';
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,233 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { mountWithIntl, findTestSubject } from '@kbn/test-jest-helpers';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
import { keys } from '@elastic/eui';
|
||||
|
||||
// This import needs to come before './code_editor' below as it sets the jest.mocks
|
||||
import { mockedEditorInstance } from './code_editor.test.helpers';
|
||||
|
||||
import { CodeEditor } from './code_editor';
|
||||
|
||||
// disabled because this is a test, but also it seems we shouldn't need this?
|
||||
/* eslint-disable-next-line @kbn/eslint/module_migration */
|
||||
import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
|
||||
|
||||
// A sample language definition with a few example tokens
|
||||
const simpleLogLang: monaco.languages.IMonarchLanguage = {
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/\[error.*/, 'constant'],
|
||||
[/\[notice.*/, 'variable'],
|
||||
[/\[info.*/, 'string'],
|
||||
[/\[[a-zA-Z 0-9:]+\]/, 'tag'],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const logs = `
|
||||
[Sun Mar 7 20:54:27 2004] [notice] [client xx.xx.xx.xx] This is a notice!
|
||||
[Sun Mar 7 20:58:27 2004] [info] [client xx.xx.xx.xx] (104)Connection reset by peer: client stopped connection before send body completed
|
||||
[Sun Mar 7 21:16:17 2004] [error] [client xx.xx.xx.xx] File does not exist: /home/httpd/twiki/view/Main/WebHome
|
||||
`;
|
||||
|
||||
describe('<CodeEditor />', () => {
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(), // deprecated
|
||||
removeListener: jest.fn(), // deprecated
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
window.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
};
|
||||
|
||||
monaco.languages.register({ id: 'loglang' });
|
||||
monaco.languages.setMonarchTokensProvider('loglang', simpleLogLang);
|
||||
});
|
||||
|
||||
test('is rendered', () => {
|
||||
const component = mountWithIntl(
|
||||
<CodeEditor languageId="loglang" height={250} value={logs} onChange={() => {}} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('editor mount setup', () => {
|
||||
const suggestionProvider = {
|
||||
provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => ({
|
||||
suggestions: [],
|
||||
}),
|
||||
};
|
||||
const hoverProvider = {
|
||||
provideHover: (model: monaco.editor.ITextModel, position: monaco.Position) => ({
|
||||
contents: [],
|
||||
}),
|
||||
};
|
||||
|
||||
const editorWillMount = jest.fn();
|
||||
|
||||
monaco.languages.onLanguage = jest.fn((languageId, func) => {
|
||||
expect(languageId).toBe('loglang');
|
||||
|
||||
// Call the function immediately so we can see our providers
|
||||
// get setup without a monaco editor setting up completely
|
||||
func();
|
||||
}) as any;
|
||||
|
||||
monaco.languages.registerCompletionItemProvider = jest.fn();
|
||||
monaco.languages.registerSignatureHelpProvider = jest.fn();
|
||||
monaco.languages.registerHoverProvider = jest.fn();
|
||||
|
||||
monaco.editor.defineTheme = jest.fn();
|
||||
|
||||
mountWithIntl(
|
||||
<CodeEditor
|
||||
languageId="loglang"
|
||||
value={logs}
|
||||
onChange={() => {}}
|
||||
editorWillMount={editorWillMount}
|
||||
suggestionProvider={suggestionProvider}
|
||||
hoverProvider={hoverProvider}
|
||||
/>
|
||||
);
|
||||
|
||||
// Verify our mount callback will be called
|
||||
expect(editorWillMount.mock.calls.length).toBe(1);
|
||||
|
||||
// Verify that both, default and transparent theme will be setup and then redefined as new values come through from the theme$ observable
|
||||
expect((monaco.editor.defineTheme as jest.Mock).mock.calls.length).toBe(4);
|
||||
|
||||
// Verify our language features have been registered
|
||||
expect((monaco.languages.onLanguage as jest.Mock).mock.calls.length).toBe(1);
|
||||
expect((monaco.languages.registerCompletionItemProvider as jest.Mock).mock.calls.length).toBe(
|
||||
1
|
||||
);
|
||||
expect((monaco.languages.registerHoverProvider as jest.Mock).mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
describe('hint element', () => {
|
||||
let component: ReactWrapper;
|
||||
const getHint = (): ReactWrapper => findTestSubject(component, 'codeEditorHint');
|
||||
|
||||
beforeEach(() => {
|
||||
component = mountWithIntl(
|
||||
<CodeEditor languageId="loglang" height={250} value={logs} onChange={() => {}} />
|
||||
);
|
||||
});
|
||||
|
||||
test('should be tabable', () => {
|
||||
const DOMnode = getHint().getDOMNode();
|
||||
expect(DOMnode.getAttribute('tabindex')).toBe('0');
|
||||
expect(DOMnode).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should be disabled when the ui monaco editor gains focus', async () => {
|
||||
// Initially it is visible and active
|
||||
expect((getHint().props() as any).className).not.toContain('isInactive');
|
||||
|
||||
getHint().simulate('keydown', { key: keys.ENTER });
|
||||
|
||||
expect((getHint().props() as any).className).toContain('isInactive');
|
||||
});
|
||||
|
||||
test('should be enabled when hitting the ESC key', () => {
|
||||
getHint().simulate('keydown', { key: keys.ENTER });
|
||||
|
||||
expect((getHint().props() as any).className).toContain('isInactive');
|
||||
|
||||
findTestSubject(component, 'monacoEditorTextarea').simulate('keydown', {
|
||||
keyCode: monaco.KeyCode.Escape,
|
||||
});
|
||||
|
||||
expect((getHint().props() as any).className).not.toContain('isInactive');
|
||||
});
|
||||
|
||||
test('should detect that the suggestion menu is open and not show the hint on ESC', async () => {
|
||||
getHint().simulate('keydown', { key: keys.ENTER });
|
||||
|
||||
expect((getHint().props() as any).className).toContain('isInactive');
|
||||
expect(mockedEditorInstance?.__helpers__.areSuggestionsVisible()).toBe(false);
|
||||
|
||||
// Show the suggestions in the editor
|
||||
mockedEditorInstance?.__helpers__.showSuggestions();
|
||||
expect(mockedEditorInstance?.__helpers__.areSuggestionsVisible()).toBe(true);
|
||||
|
||||
// Hitting the ESC key with the suggestions visible
|
||||
findTestSubject(component, 'monacoEditorTextarea').simulate('keydown', {
|
||||
keyCode: monaco.KeyCode.Escape,
|
||||
});
|
||||
|
||||
expect(mockedEditorInstance?.__helpers__.areSuggestionsVisible()).toBe(false);
|
||||
|
||||
// The keyboard hint is still **not** active
|
||||
expect((getHint().props() as any).className).toContain('isInactive');
|
||||
|
||||
// Hitting a second time the ESC key should now show the hint
|
||||
findTestSubject(component, 'monacoEditorTextarea').simulate('keydown', {
|
||||
keyCode: monaco.KeyCode.Escape,
|
||||
});
|
||||
|
||||
expect((getHint().props() as any).className).not.toContain('isInactive');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test whether our custom placeholder widget is being mounted based on our React logic. We cannot do a full
|
||||
* test with Monaco so the parts handled by Monaco are all mocked out and we just check whether the element is mounted
|
||||
* in the DOM.
|
||||
*/
|
||||
describe('placeholder element', () => {
|
||||
let component: ReactWrapper;
|
||||
const getPlaceholderDomElement = (): HTMLElement | null =>
|
||||
component.getDOMNode().querySelector('.kibanaCodeEditor__placeholderContainer');
|
||||
|
||||
beforeEach(() => {
|
||||
component = mountWithIntl(
|
||||
<CodeEditor
|
||||
languageId="loglang"
|
||||
height={250}
|
||||
value=""
|
||||
onChange={() => {}}
|
||||
placeholder="myplaceholder"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('displays placeholder element when placeholder text is provided', () => {
|
||||
expect(getPlaceholderDomElement()?.innerText).toBe('myplaceholder');
|
||||
});
|
||||
|
||||
it('does not display placeholder element when placeholder text is not provided', () => {
|
||||
component.setProps({ ...component.props(), placeholder: undefined, value: '' });
|
||||
component.update();
|
||||
expect(getPlaceholderDomElement()).toBe(null);
|
||||
});
|
||||
|
||||
it('does not display placeholder element when user input has been provided', () => {
|
||||
component.setProps({ ...component.props(), value: 'some input' });
|
||||
component.update();
|
||||
expect(getPlaceholderDomElement()).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,587 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useCallback, useMemo, useEffect, KeyboardEvent } from 'react';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import ReactMonacoEditor from 'react-monaco-editor';
|
||||
import {
|
||||
htmlIdGenerator,
|
||||
EuiToolTip,
|
||||
keys,
|
||||
EuiButtonIcon,
|
||||
EuiOverlayMask,
|
||||
EuiI18n,
|
||||
EuiFocusTrap,
|
||||
EuiCopy,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { monaco } from '@kbn/monaco';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import classNames from 'classnames';
|
||||
import './register_languages';
|
||||
import { remeasureFonts } from './remeasure_fonts';
|
||||
export type { CodeEditorProps as Props } from '@kbn/code-editor';
|
||||
|
||||
import {
|
||||
DARK_THEME,
|
||||
LIGHT_THEME,
|
||||
DARK_THEME_TRANSPARENT,
|
||||
LIGHT_THEME_TRANSPARENT,
|
||||
} from './editor_theme';
|
||||
|
||||
import { PlaceholderWidget } from './placeholder_widget';
|
||||
|
||||
import './editor.scss';
|
||||
|
||||
export interface Props {
|
||||
/** Width of editor. Defaults to 100%. */
|
||||
width?: string | number;
|
||||
|
||||
/** Height of editor. Defaults to 100%. */
|
||||
height?: string | number;
|
||||
|
||||
/** ID of the editor language */
|
||||
languageId: string;
|
||||
|
||||
/** Value of the editor */
|
||||
value: string;
|
||||
|
||||
/** Function invoked when text in editor is changed */
|
||||
onChange?: (value: string, event: monaco.editor.IModelContentChangedEvent) => void;
|
||||
|
||||
/**
|
||||
* Options for the Monaco Code Editor
|
||||
* Documentation of options can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
|
||||
*/
|
||||
options?: monaco.editor.IStandaloneEditorConstructionOptions;
|
||||
|
||||
/**
|
||||
* Suggestion provider for autocompletion
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.completionitemprovider.html
|
||||
*/
|
||||
suggestionProvider?: monaco.languages.CompletionItemProvider;
|
||||
|
||||
/**
|
||||
* Signature provider for function parameter info
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.signaturehelpprovider.html
|
||||
*/
|
||||
signatureProvider?: monaco.languages.SignatureHelpProvider;
|
||||
|
||||
/**
|
||||
* Hover provider for hover documentation
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.hoverprovider.html
|
||||
*/
|
||||
hoverProvider?: monaco.languages.HoverProvider;
|
||||
|
||||
/**
|
||||
* Language config provider for bracket
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.languageconfiguration.html
|
||||
*/
|
||||
languageConfiguration?: monaco.languages.LanguageConfiguration;
|
||||
|
||||
/**
|
||||
* Function called before the editor is mounted in the view
|
||||
*/
|
||||
editorWillMount?: () => void;
|
||||
/**
|
||||
* Function called before the editor is mounted in the view
|
||||
* and completely replaces the setup behavior called by the component
|
||||
*/
|
||||
overrideEditorWillMount?: () => void;
|
||||
|
||||
/**
|
||||
* Function called after the editor is mounted in the view
|
||||
*/
|
||||
editorDidMount?: (editor: monaco.editor.IStandaloneCodeEditor) => void;
|
||||
|
||||
/**
|
||||
* Should the editor use the dark theme
|
||||
*/
|
||||
useDarkTheme?: boolean;
|
||||
|
||||
/**
|
||||
* Should the editor use a transparent background
|
||||
*/
|
||||
transparentBackground?: boolean;
|
||||
|
||||
/**
|
||||
* Should the editor be rendered using the fullWidth EUI attribute
|
||||
*/
|
||||
fullWidth?: boolean;
|
||||
|
||||
placeholder?: string;
|
||||
/**
|
||||
* Accessible name for the editor. (Defaults to "Code editor")
|
||||
*/
|
||||
'aria-label'?: string;
|
||||
|
||||
isCopyable?: boolean;
|
||||
allowFullScreen?: boolean;
|
||||
}
|
||||
|
||||
export const CodeEditor: React.FC<Props> = ({
|
||||
languageId,
|
||||
value,
|
||||
onChange,
|
||||
width,
|
||||
height,
|
||||
options,
|
||||
overrideEditorWillMount,
|
||||
editorDidMount,
|
||||
editorWillMount,
|
||||
useDarkTheme: useDarkThemeProp,
|
||||
transparentBackground,
|
||||
suggestionProvider,
|
||||
signatureProvider,
|
||||
hoverProvider,
|
||||
placeholder,
|
||||
languageConfiguration,
|
||||
'aria-label': ariaLabel = i18n.translate('kibana-react.kibanaCodeEditor.ariaLabel', {
|
||||
defaultMessage: 'Code Editor',
|
||||
}),
|
||||
isCopyable = false,
|
||||
allowFullScreen = false,
|
||||
}) => {
|
||||
const { colorMode } = useEuiTheme();
|
||||
const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK';
|
||||
|
||||
// We need to be able to mock the MonacoEditor in our test in order to not test implementation
|
||||
// detail and not have to call methods on the <CodeEditor /> component instance.
|
||||
const MonacoEditor: typeof ReactMonacoEditor = useMemo(() => {
|
||||
const isMockedComponent =
|
||||
typeof ReactMonacoEditor === 'function' && ReactMonacoEditor.name === 'JestMockEditor';
|
||||
return isMockedComponent
|
||||
? (ReactMonacoEditor as unknown as () => typeof ReactMonacoEditor)()
|
||||
: ReactMonacoEditor;
|
||||
}, []);
|
||||
|
||||
const { FullScreenDisplay, FullScreenButton, isFullScreen, setIsFullScreen, onKeyDown } =
|
||||
useFullScreen({
|
||||
allowFullScreen,
|
||||
});
|
||||
|
||||
const isReadOnly = options?.readOnly ?? false;
|
||||
|
||||
const _editor = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
|
||||
const _placeholderWidget = useRef<PlaceholderWidget | null>(null);
|
||||
const isSuggestionMenuOpen = useRef(false);
|
||||
const editorHint = useRef<HTMLDivElement>(null);
|
||||
const textboxMutationObserver = useRef<MutationObserver | null>(null);
|
||||
|
||||
const [isHintActive, setIsHintActive] = useState(true);
|
||||
|
||||
const promptClasses = classNames('kibanaCodeEditor__keyboardHint', {
|
||||
'kibanaCodeEditor__keyboardHint--isInactive': !isHintActive,
|
||||
});
|
||||
|
||||
const _updateDimensions = useCallback(() => {
|
||||
_editor.current?.layout();
|
||||
}, []);
|
||||
|
||||
useResizeDetector({
|
||||
handleWidth: true,
|
||||
handleHeight: true,
|
||||
onResize: _updateDimensions,
|
||||
refreshMode: 'debounce',
|
||||
});
|
||||
|
||||
const startEditing = useCallback(() => {
|
||||
setIsHintActive(false);
|
||||
_editor.current?.focus();
|
||||
}, []);
|
||||
|
||||
const stopEditing = useCallback(() => {
|
||||
setIsHintActive(true);
|
||||
}, []);
|
||||
|
||||
const onKeyDownHint = useCallback(
|
||||
(ev: React.KeyboardEvent) => {
|
||||
if (ev.key === keys.ENTER) {
|
||||
ev.preventDefault();
|
||||
startEditing();
|
||||
}
|
||||
},
|
||||
[startEditing]
|
||||
);
|
||||
|
||||
const onKeydownMonaco = useCallback(
|
||||
(ev: monaco.IKeyboardEvent) => {
|
||||
if (ev.keyCode === monaco.KeyCode.Escape) {
|
||||
// If the autocompletion context menu is open then we want to let ESCAPE close it but
|
||||
// **not** exit out of editing mode.
|
||||
if (!isSuggestionMenuOpen.current) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
stopEditing();
|
||||
editorHint.current?.focus();
|
||||
}
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
},
|
||||
[stopEditing]
|
||||
);
|
||||
|
||||
const onBlurMonaco = useCallback(() => {
|
||||
stopEditing();
|
||||
}, [stopEditing]);
|
||||
|
||||
const renderPrompt = useCallback(() => {
|
||||
const enterKey = (
|
||||
<strong>
|
||||
{i18n.translate('kibana-react.kibanaCodeEditor.enterKeyLabel', {
|
||||
defaultMessage: 'Enter',
|
||||
description:
|
||||
'The name used for the Enter key on keyword. Will be {key} in kibana-react.kibanaCodeEditor.startEditing(ReadOnly).',
|
||||
})}
|
||||
</strong>
|
||||
);
|
||||
|
||||
const escapeKey = (
|
||||
<strong>
|
||||
{i18n.translate('kibana-react.kibanaCodeEditor.escapeKeyLabel', {
|
||||
defaultMessage: 'Esc',
|
||||
description:
|
||||
'The label of the Escape key as printed on the keyboard. Will be {key} inside kibana-react.kibanaCodeEditor.stopEditing(ReadOnly).',
|
||||
})}
|
||||
</strong>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
display="block"
|
||||
content={
|
||||
<>
|
||||
<p>
|
||||
{isReadOnly ? (
|
||||
<FormattedMessage
|
||||
id="kibana-react.kibanaCodeEditor.startEditingReadOnly"
|
||||
defaultMessage="Press {key} to start interacting with the code."
|
||||
values={{ key: enterKey }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="kibana-react.kibanaCodeEditor.startEditing"
|
||||
defaultMessage="Press {key} to start editing."
|
||||
values={{ key: enterKey }}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{isReadOnly ? (
|
||||
<FormattedMessage
|
||||
id="kibana-react.kibanaCodeEditor.stopEditingReadOnly"
|
||||
defaultMessage="Press {key} to stop interacting with the code."
|
||||
values={{ key: escapeKey }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="kibana-react.kibanaCodeEditor.stopEditing"
|
||||
defaultMessage="Press {key} to stop editing."
|
||||
values={{ key: escapeKey }}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={promptClasses}
|
||||
id={htmlIdGenerator('codeEditor')()}
|
||||
ref={editorHint}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={startEditing}
|
||||
onKeyDown={onKeyDownHint}
|
||||
aria-label={ariaLabel}
|
||||
data-test-subj="codeEditorHint"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}, [onKeyDownHint, promptClasses, startEditing]);
|
||||
|
||||
const _editorWillMount = useCallback(
|
||||
(__monaco: unknown) => {
|
||||
if (__monaco !== monaco) {
|
||||
throw new Error('react-monaco-editor is using a different version of monaco');
|
||||
}
|
||||
|
||||
if (overrideEditorWillMount) {
|
||||
overrideEditorWillMount();
|
||||
return;
|
||||
}
|
||||
|
||||
editorWillMount?.();
|
||||
|
||||
monaco.languages.onLanguage(languageId, () => {
|
||||
if (suggestionProvider) {
|
||||
monaco.languages.registerCompletionItemProvider(languageId, suggestionProvider);
|
||||
}
|
||||
|
||||
if (signatureProvider) {
|
||||
monaco.languages.registerSignatureHelpProvider(languageId, signatureProvider);
|
||||
}
|
||||
|
||||
if (hoverProvider) {
|
||||
monaco.languages.registerHoverProvider(languageId, hoverProvider);
|
||||
}
|
||||
|
||||
if (languageConfiguration) {
|
||||
monaco.languages.setLanguageConfiguration(languageId, languageConfiguration);
|
||||
}
|
||||
});
|
||||
|
||||
// Register themes
|
||||
monaco.editor.defineTheme('euiColors', useDarkTheme ? DARK_THEME : LIGHT_THEME);
|
||||
monaco.editor.defineTheme(
|
||||
'euiColorsTransparent',
|
||||
useDarkTheme ? DARK_THEME_TRANSPARENT : LIGHT_THEME_TRANSPARENT
|
||||
);
|
||||
},
|
||||
[
|
||||
overrideEditorWillMount,
|
||||
editorWillMount,
|
||||
languageId,
|
||||
useDarkTheme,
|
||||
suggestionProvider,
|
||||
signatureProvider,
|
||||
hoverProvider,
|
||||
languageConfiguration,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Register themes when 'useDarkThem' changes
|
||||
monaco.editor.defineTheme('euiColors', useDarkTheme ? DARK_THEME : LIGHT_THEME);
|
||||
monaco.editor.defineTheme(
|
||||
'euiColorsTransparent',
|
||||
useDarkTheme ? DARK_THEME_TRANSPARENT : LIGHT_THEME_TRANSPARENT
|
||||
);
|
||||
}, [useDarkTheme]);
|
||||
|
||||
const _editorDidMount = useCallback(
|
||||
(editor: monaco.editor.IStandaloneCodeEditor, __monaco: unknown) => {
|
||||
if (__monaco !== monaco) {
|
||||
throw new Error('react-monaco-editor is using a different version of monaco');
|
||||
}
|
||||
|
||||
remeasureFonts();
|
||||
|
||||
_editor.current = editor;
|
||||
|
||||
const textbox = editor.getDomNode()?.getElementsByTagName('textarea')[0];
|
||||
if (textbox) {
|
||||
// Make sure the textarea is not directly accesible with TAB
|
||||
textbox.tabIndex = -1;
|
||||
|
||||
// The Monaco editor seems to override the tabindex and set it back to "0"
|
||||
// so we make sure that whenever the attributes change the tabindex stays at -1
|
||||
textboxMutationObserver.current = new MutationObserver(function onTextboxAttributeChange() {
|
||||
if (textbox.tabIndex >= 0) {
|
||||
textbox.tabIndex = -1;
|
||||
}
|
||||
});
|
||||
textboxMutationObserver.current.observe(textbox, { attributes: true });
|
||||
}
|
||||
|
||||
editor.onKeyDown(onKeydownMonaco);
|
||||
editor.onDidBlurEditorText(onBlurMonaco);
|
||||
|
||||
// "widget" is not part of the TS interface but does exist
|
||||
// @ts-expect-errors
|
||||
const suggestionWidget = editor.getContribution('editor.contrib.suggestController')?.widget
|
||||
?.value;
|
||||
|
||||
// As I haven't found official documentation for "onDidShow" and "onDidHide"
|
||||
// we guard from possible changes in the underlying lib
|
||||
if (suggestionWidget && suggestionWidget.onDidShow && suggestionWidget.onDidHide) {
|
||||
suggestionWidget.onDidShow(() => {
|
||||
isSuggestionMenuOpen.current = true;
|
||||
});
|
||||
suggestionWidget.onDidHide(() => {
|
||||
isSuggestionMenuOpen.current = false;
|
||||
});
|
||||
}
|
||||
|
||||
editorDidMount?.(editor);
|
||||
},
|
||||
[editorDidMount]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
textboxMutationObserver.current?.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (placeholder && !value && _editor.current) {
|
||||
// Mounts editor inside constructor
|
||||
_placeholderWidget.current = new PlaceholderWidget(placeholder, _editor.current);
|
||||
}
|
||||
return () => {
|
||||
_placeholderWidget.current?.dispose();
|
||||
_placeholderWidget.current = null;
|
||||
};
|
||||
}, [placeholder, value]);
|
||||
|
||||
const { CopyButton } = useCopy({ isCopyable, value });
|
||||
|
||||
return (
|
||||
<div className="kibanaCodeEditor" onKeyDown={onKeyDown} data-test-subj="kibanaCodeEditor">
|
||||
{renderPrompt()}
|
||||
|
||||
<FullScreenDisplay>
|
||||
{allowFullScreen || isCopyable ? (
|
||||
<div className="kibanaCodeEditor__controls">
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<CopyButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<FullScreenButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
) : null}
|
||||
<MonacoEditor
|
||||
theme={options?.theme ?? (transparentBackground ? 'euiColorsTransparent' : 'euiColors')}
|
||||
language={languageId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
width={isFullScreen ? '100vw' : width}
|
||||
height={isFullScreen ? '100vh' : height}
|
||||
editorWillMount={_editorWillMount}
|
||||
editorDidMount={_editorDidMount}
|
||||
options={{
|
||||
padding: allowFullScreen || isCopyable ? { top: 24 } : {},
|
||||
renderLineHighlight: 'none',
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
scrollbar: {
|
||||
useShadows: false,
|
||||
// Scroll events are handled only when there is scrollable content. When there is scrollable content, the
|
||||
// editor should scroll to the bottom then break out of that scroll context and continue scrolling on any
|
||||
// outer scrollbars.
|
||||
alwaysConsumeMouseWheel: false,
|
||||
},
|
||||
wordBasedSuggestions: false,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'indent',
|
||||
matchBrackets: 'never',
|
||||
fontFamily: 'Roboto Mono',
|
||||
fontSize: isFullScreen ? 16 : 12,
|
||||
lineHeight: isFullScreen ? 24 : 21,
|
||||
...options,
|
||||
}}
|
||||
/>
|
||||
</FullScreenDisplay>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fullscreen logic
|
||||
*/
|
||||
|
||||
const useFullScreen = ({ allowFullScreen }: { allowFullScreen?: boolean }) => {
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
|
||||
const toggleFullScreen = () => {
|
||||
setIsFullScreen(!isFullScreen);
|
||||
};
|
||||
|
||||
const onKeyDown = useCallback((event: KeyboardEvent<HTMLElement>) => {
|
||||
if (event.key === keys.ESCAPE) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const FullScreenButton: React.FC = () => {
|
||||
if (!allowFullScreen) return null;
|
||||
return (
|
||||
<EuiI18n
|
||||
tokens={['euiCodeBlock.fullscreenCollapse', 'euiCodeBlock.fullscreenExpand']}
|
||||
defaults={['Collapse', 'Expand']}
|
||||
>
|
||||
{([fullscreenCollapse, fullscreenExpand]: string[]) => (
|
||||
<EuiButtonIcon
|
||||
className="euiCodeBlock__fullScreenButton"
|
||||
onClick={toggleFullScreen}
|
||||
iconType={isFullScreen ? 'fullScreenExit' : 'fullScreen'}
|
||||
color="text"
|
||||
aria-label={isFullScreen ? fullscreenCollapse : fullscreenExpand}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</EuiI18n>
|
||||
);
|
||||
};
|
||||
|
||||
const FullScreenDisplay = useMemo(
|
||||
() =>
|
||||
({ children }: { children: Array<JSX.Element | null> | JSX.Element }) => {
|
||||
if (!isFullScreen) return <>{children}</>;
|
||||
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiFocusTrap clickOutsideDisables={true}>
|
||||
<div className={'kibanaCodeEditor__isFullScreen'}>{children}</div>
|
||||
</EuiFocusTrap>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
},
|
||||
[isFullScreen]
|
||||
);
|
||||
|
||||
return {
|
||||
FullScreenButton,
|
||||
FullScreenDisplay,
|
||||
onKeyDown,
|
||||
isFullScreen,
|
||||
setIsFullScreen,
|
||||
};
|
||||
};
|
||||
|
||||
const useCopy = ({ isCopyable, value }: { isCopyable: boolean; value: string }) => {
|
||||
const showCopyButton = isCopyable && value;
|
||||
|
||||
const CopyButton = () => {
|
||||
if (!showCopyButton) return null;
|
||||
|
||||
return (
|
||||
<div className="euiCodeBlock__copyButton">
|
||||
<EuiI18n token="euiCodeBlock.copyButton" default="Copy">
|
||||
{(copyButton: string) => (
|
||||
<EuiCopy textToCopy={value}>
|
||||
{(copy) => (
|
||||
<EuiButtonIcon
|
||||
onClick={copy}
|
||||
iconType="copyClipboard"
|
||||
color="text"
|
||||
aria-label={copyButton}
|
||||
size="xs"
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
)}
|
||||
</EuiI18n>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return { showCopyButton, CopyButton };
|
||||
};
|
||||
import { CodeEditor } from '@kbn/code-editor';
|
||||
export { CodeEditor };
|
||||
|
||||
// React.lazy requires default export
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
.react-monaco-editor-container .monaco-editor .inputarea:focus {
|
||||
animation: none !important; // Removes textarea EUI blue underline animation from EUI
|
||||
}
|
||||
|
||||
.kibanaCodeEditor {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
&__placeholderContainer {
|
||||
color: $euiTextSubduedColor;
|
||||
width: max-content;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__keyboardHint {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
&:focus {
|
||||
z-index: $euiZLevel1;
|
||||
}
|
||||
|
||||
&--isInactive {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
top: $euiSizeXS;
|
||||
right: $euiSize;
|
||||
position: absolute;
|
||||
z-index: 1000
|
||||
}
|
||||
|
||||
&__isFullScreen {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
.kibanaCodeEditor__controls {
|
||||
top: $euiSizeL;
|
||||
right: $euiSizeL;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import { EuiDelayRender, EuiErrorBoundary, EuiSkeletonText, useEuiTheme } from '
|
|||
|
||||
import type { Props } from './code_editor';
|
||||
|
||||
export * from './languages/constants';
|
||||
export * from '@kbn/code-editor/languages/constants';
|
||||
|
||||
const LazyBaseEditor = React.lazy(() => import('./code_editor'));
|
||||
const LazyCodeEditorField = React.lazy(() =>
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is adapted from: https://github.com/microsoft/monaco-languages/blob/master/src/handlebars/handlebars.ts
|
||||
* License: https://github.com/microsoft/monaco-languages/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
|
||||
comments: {
|
||||
blockComment: ['{{!--', '--}}'],
|
||||
},
|
||||
|
||||
brackets: [
|
||||
['<', '>'],
|
||||
['{{', '}}'],
|
||||
['{', '}'],
|
||||
['(', ')'],
|
||||
],
|
||||
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
|
||||
surroundingPairs: [
|
||||
{ open: '<', close: '>' },
|
||||
{ open: '"', close: '"' },
|
||||
{ open: "'", close: "'" },
|
||||
],
|
||||
};
|
||||
|
||||
export const lexerRules: monaco.languages.IMonarchLanguage = {
|
||||
// Set defaultToken to invalid to see what you do not tokenize yet.
|
||||
defaultToken: 'invalid',
|
||||
tokenPostfix: '',
|
||||
brackets: [
|
||||
{
|
||||
token: 'constant.delimiter.double',
|
||||
open: '{{',
|
||||
close: '}}',
|
||||
},
|
||||
{
|
||||
token: 'constant.delimiter.triple',
|
||||
open: '{{{',
|
||||
close: '}}}',
|
||||
},
|
||||
],
|
||||
|
||||
tokenizer: {
|
||||
root: [
|
||||
{ include: '@maybeHandlebars' },
|
||||
{ include: '@whitespace' },
|
||||
{ include: '@urlScheme' },
|
||||
{ include: '@urlAuthority' },
|
||||
{ include: '@urlSlash' },
|
||||
{ include: '@urlParamKey' },
|
||||
{ include: '@urlParamValue' },
|
||||
{ include: '@text' },
|
||||
],
|
||||
|
||||
maybeHandlebars: [
|
||||
[
|
||||
/\{\{/,
|
||||
{
|
||||
token: '@rematch',
|
||||
switchTo: '@handlebars.root',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
whitespace: [[/[ \t\r\n]+/, '']],
|
||||
|
||||
text: [
|
||||
[
|
||||
/[^<{\?\&\/]+/,
|
||||
{
|
||||
token: 'text',
|
||||
next: '@popall',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
rematchAsRoot: [
|
||||
[
|
||||
/.+/,
|
||||
{
|
||||
token: '@rematch',
|
||||
switchTo: '@root',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
urlScheme: [
|
||||
[
|
||||
/([a-zA-Z0-9\+\.\-]{1,10})(:)/,
|
||||
[
|
||||
{
|
||||
token: 'text.keyword.scheme.url',
|
||||
},
|
||||
{
|
||||
token: 'delimiter',
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
urlAuthority: [
|
||||
[
|
||||
/(\/\/)([a-zA-Z0-9\.\-_]+)/,
|
||||
[
|
||||
{
|
||||
token: 'delimiter',
|
||||
},
|
||||
{
|
||||
token: 'metatag.keyword.authority.url',
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
urlSlash: [
|
||||
[
|
||||
/\/+/,
|
||||
{
|
||||
token: 'delimiter',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
urlParamKey: [
|
||||
[
|
||||
/([\?\&\#])([a-zA-Z0-9_\-]+)/,
|
||||
[
|
||||
{
|
||||
token: 'delimiter.key.query.url',
|
||||
},
|
||||
{
|
||||
token: 'label.label.key.query.url',
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
urlParamValue: [
|
||||
[
|
||||
/(\=)([^\?\&\{}]+)/,
|
||||
[
|
||||
{
|
||||
token: 'text.separator.value.query.url',
|
||||
},
|
||||
{
|
||||
token: 'text.value.query.url',
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
handlebars: [
|
||||
[
|
||||
/\{\{\{?/,
|
||||
{
|
||||
token: '@brackets',
|
||||
bracket: '@open',
|
||||
},
|
||||
],
|
||||
[
|
||||
/\}\}\}?/,
|
||||
{
|
||||
token: '@brackets',
|
||||
bracket: '@close',
|
||||
switchTo: '@$S2.$S3',
|
||||
},
|
||||
],
|
||||
{ include: 'handlebarsExpression' },
|
||||
],
|
||||
|
||||
handlebarsExpression: [
|
||||
[/"[^"]*"/, 'string.handlebars'],
|
||||
[/[#/][^\s}]+/, 'keyword.helper.handlebars'],
|
||||
[/else\b/, 'keyword.helper.handlebars'],
|
||||
[/[\s]+/],
|
||||
[/[^}]/, 'variable.parameter.handlebars'],
|
||||
],
|
||||
},
|
||||
} as monaco.languages.IMonarchLanguage;
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
],
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '"', close: '"', notIn: ['string'] },
|
||||
],
|
||||
comments: {
|
||||
lineComment: '//',
|
||||
blockComment: ['/*', '*/'],
|
||||
},
|
||||
};
|
||||
|
||||
export const lexerRules: monaco.languages.IMonarchLanguage = {
|
||||
defaultToken: '',
|
||||
tokenPostfix: '',
|
||||
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
|
||||
digits: /-?(?:0|[1-9]\d*)(?:(?:\.\d+)?(?:[eE][+-]?\d+)?)?/,
|
||||
symbols: /[,:]+/,
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/(@digits)n?/, 'number'],
|
||||
[/(@symbols)n?/, 'delimiter'],
|
||||
|
||||
{ include: '@keyword' },
|
||||
{ include: '@url' },
|
||||
{ include: '@whitespace' },
|
||||
{ include: '@brackets' },
|
||||
{ include: '@keyName' },
|
||||
{ include: '@string' },
|
||||
],
|
||||
|
||||
keyword: [[/(?:true|false|null)\b/, 'keyword']],
|
||||
|
||||
url: [
|
||||
[
|
||||
/(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/,
|
||||
'string',
|
||||
],
|
||||
],
|
||||
|
||||
keyName: [[/(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*(?=:)/, 'variable']],
|
||||
|
||||
brackets: [[/{/, '@push'], [/}/, '@pop'], [/[[(]/], [/[\])]/]],
|
||||
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, ''],
|
||||
[/\/\*/, 'comment', '@comment'],
|
||||
[/\/\/.*$/, 'comment'],
|
||||
],
|
||||
|
||||
comment: [
|
||||
[/[^\/*]+/, 'comment'],
|
||||
[/\*\//, 'comment', '@pop'],
|
||||
[/[\/*]/, 'comment'],
|
||||
],
|
||||
|
||||
string: [
|
||||
[/(?:[^,\{\[\}\]\s]+|"(?:[^"\\]|\\.)*")\s*/, 'string'],
|
||||
[/"""/, 'string', '@stringLiteral'],
|
||||
[/"/, 'string', '@stringDouble'],
|
||||
],
|
||||
|
||||
stringDouble: [
|
||||
[/[^\\"]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/"/, 'string', '@pop'],
|
||||
],
|
||||
|
||||
stringLiteral: [
|
||||
[/"""/, 'string', '@pop'],
|
||||
[/\\""""/, 'string', '@pop'],
|
||||
[/./, 'string'],
|
||||
],
|
||||
},
|
||||
} as monaco.languages.IMonarchLanguage;
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
export class PlaceholderWidget implements monaco.editor.IContentWidget {
|
||||
constructor(
|
||||
private readonly placeholderText: string,
|
||||
private readonly editor: monaco.editor.ICodeEditor
|
||||
) {
|
||||
editor.addContentWidget(this);
|
||||
}
|
||||
|
||||
private domNode: undefined | HTMLElement;
|
||||
|
||||
public getId(): string {
|
||||
return 'KBN_CODE_EDITOR_PLACEHOLDER_WIDGET_ID';
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
if (!this.domNode) {
|
||||
const domNode = document.createElement('div');
|
||||
domNode.innerText = this.placeholderText;
|
||||
domNode.className = 'kibanaCodeEditor__placeholderContainer';
|
||||
this.editor.applyFontInfo(domNode);
|
||||
this.domNode = domNode;
|
||||
}
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
public getPosition(): monaco.editor.IContentWidgetPosition | null {
|
||||
return {
|
||||
position: {
|
||||
column: 1,
|
||||
lineNumber: 1,
|
||||
},
|
||||
preference: [monaco.editor.ContentWidgetPositionPreference.EXACT],
|
||||
};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.editor.removeContentWidget(this);
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { monaco } from '@kbn/monaco';
|
||||
|
||||
/**
|
||||
* When using custom fonts with monaco need to call `monaco.editor.remeasureFonts()` when custom fonts finished loading
|
||||
* Otherwise initial measurements on fallback font are used which causes visual glitches in the editor
|
||||
*/
|
||||
export function remeasureFonts() {
|
||||
if ('fonts' in window.document && 'ready' in window.document.fonts) {
|
||||
window.document.fonts.ready
|
||||
.then(() => {
|
||||
monaco.editor.remeasureFonts();
|
||||
})
|
||||
.catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Failed to remeasureFonts in <CodeEditor/>');
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(e);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
"@kbn/core-theme-browser",
|
||||
"@kbn/react-kibana-context-common",
|
||||
"@kbn/react-kibana-context-styled",
|
||||
"@kbn/code-editor",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -150,12 +150,8 @@
|
|||
"@kbn/cloud-plugin/*": ["x-pack/plugins/cloud/*"],
|
||||
"@kbn/cloud-security-posture-plugin": ["x-pack/plugins/cloud_security_posture"],
|
||||
"@kbn/cloud-security-posture-plugin/*": ["x-pack/plugins/cloud_security_posture/*"],
|
||||
"@kbn/code-editor": ["packages/shared-ux/code_editor/impl"],
|
||||
"@kbn/code-editor/*": ["packages/shared-ux/code_editor/impl/*"],
|
||||
"@kbn/code-editor-mocks": ["packages/shared-ux/code_editor/mocks"],
|
||||
"@kbn/code-editor-mocks/*": ["packages/shared-ux/code_editor/mocks/*"],
|
||||
"@kbn/code-editor-types": ["packages/shared-ux/code_editor/types"],
|
||||
"@kbn/code-editor-types/*": ["packages/shared-ux/code_editor/types/*"],
|
||||
"@kbn/code-editor": ["packages/shared-ux/code_editor"],
|
||||
"@kbn/code-editor/*": ["packages/shared-ux/code_editor/*"],
|
||||
"@kbn/coloring": ["packages/kbn-coloring"],
|
||||
"@kbn/coloring/*": ["packages/kbn-coloring/*"],
|
||||
"@kbn/config": ["packages/kbn-config"],
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@kbn/kibana-react-plugin/public/code_editor/code_editor.test.helpers';
|
||||
import '@kbn/code-editor/code_editor.test.helpers';
|
||||
import { TestProvider } from '../../test/test_provider';
|
||||
import {
|
||||
getCloudDefendNewPolicyMock,
|
||||
|
|
|
@ -17,7 +17,7 @@ import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
|||
import type { CloudDefendPluginStartDeps } from '../types';
|
||||
import './__mocks__/worker';
|
||||
import './__mocks__/resizeobserver';
|
||||
import '@kbn/kibana-react-plugin/public/code_editor/code_editor.test.helpers';
|
||||
import '@kbn/code-editor/code_editor.test.helpers';
|
||||
|
||||
// @ts-ignore-next
|
||||
window.Worker = Worker;
|
||||
|
|
|
@ -35,7 +35,8 @@
|
|||
"@kbn/utility-types-jest",
|
||||
"@kbn/kubernetes-security-plugin",
|
||||
"@kbn/core-http-router-server-mocks",
|
||||
"@kbn/core-elasticsearch-server"
|
||||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/code-editor"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -4716,10 +4716,6 @@
|
|||
"kibana_utils.stateManagement.url.restoreUrlErrorTitle": "Erreur lors de la restauration de l'état depuis l'URL.",
|
||||
"kibana_utils.stateManagement.url.saveStateInUrlErrorTitle": "Erreur lors de l'enregistrement de l'état dans l'URL.",
|
||||
"kibana-react.dualRangeControl.outsideOfRangeErrorMessage": "Les valeurs doivent être comprises entre {min} et {max}, inclus",
|
||||
"kibana-react.kibanaCodeEditor.startEditing": "Appuyez sur {key} pour démarrer la modification.",
|
||||
"kibana-react.kibanaCodeEditor.startEditingReadOnly": "Appuyez sur {key} pour commencer à interagir avec le code.",
|
||||
"kibana-react.kibanaCodeEditor.stopEditing": "Appuyez sur {key} pour arrêter la modification.",
|
||||
"kibana-react.kibanaCodeEditor.stopEditingReadOnly": "Appuyez sur {key} pour arrêter d'interagir avec le code.",
|
||||
"kibana-react.noDataPage.cantDecide": "Vous ne savez pas quoi utiliser ? {link}",
|
||||
"kibana-react.noDataPage.intro": "Ajoutez vos données pour commencer, ou {link} sur {solution}.",
|
||||
"kibana-react.noDataPage.welcomeTitle": "Bienvenue dans Elastic {solution} !",
|
||||
|
@ -4731,9 +4727,6 @@
|
|||
"kibana-react.kbnOverviewPageHeader.addIntegrationsButtonLabel": "Ajouter des intégrations",
|
||||
"kibana-react.kbnOverviewPageHeader.devToolsButtonLabel": "Outils de développement",
|
||||
"kibana-react.kbnOverviewPageHeader.stackManagementButtonLabel": "Gérer",
|
||||
"kibana-react.kibanaCodeEditor.ariaLabel": "Éditeur de code",
|
||||
"kibana-react.kibanaCodeEditor.enterKeyLabel": "Entrée",
|
||||
"kibana-react.kibanaCodeEditor.escapeKeyLabel": "Échap",
|
||||
"kibana-react.noDataPage.cantDecide.link": "Consultez la documentation pour en savoir plus.",
|
||||
"kibana-react.noDataPage.elasticAgentCard.description": "Utilisez Elastic Agent pour collecter de manière simple et unifiée les données de vos machines.",
|
||||
"kibana-react.noDataPage.elasticAgentCard.noPermission.description": "Cette intégration n'est pas encore activée. Votre administrateur possède les autorisations requises pour l'activer.",
|
||||
|
|
|
@ -4732,10 +4732,6 @@
|
|||
"kibana_utils.stateManagement.url.restoreUrlErrorTitle": "URLからの状態の復元エラー",
|
||||
"kibana_utils.stateManagement.url.saveStateInUrlErrorTitle": "URLでの状態の保存エラー",
|
||||
"kibana-react.dualRangeControl.outsideOfRangeErrorMessage": "値は{min}と{max}の間でなければなりません",
|
||||
"kibana-react.kibanaCodeEditor.startEditing": "編集を開始するには{key}を押してください。",
|
||||
"kibana-react.kibanaCodeEditor.startEditingReadOnly": "コードの操作を開始するには{key}を押してください。",
|
||||
"kibana-react.kibanaCodeEditor.stopEditing": "編集を停止するには{key}を押してください。",
|
||||
"kibana-react.kibanaCodeEditor.stopEditingReadOnly": "コードの操作を停止するには{key}を押してください。",
|
||||
"kibana-react.noDataPage.cantDecide": "どれを使用すべきかわからない場合は{link}",
|
||||
"kibana-react.noDataPage.intro": "データを追加して開始するか、{solution}については{link}をご覧ください。",
|
||||
"kibana-react.noDataPage.welcomeTitle": "Elastic {solution}へようこそ!",
|
||||
|
@ -4747,9 +4743,6 @@
|
|||
"kibana-react.kbnOverviewPageHeader.addIntegrationsButtonLabel": "統合の追加",
|
||||
"kibana-react.kbnOverviewPageHeader.devToolsButtonLabel": "開発ツール",
|
||||
"kibana-react.kbnOverviewPageHeader.stackManagementButtonLabel": "管理",
|
||||
"kibana-react.kibanaCodeEditor.ariaLabel": "コードエディター",
|
||||
"kibana-react.kibanaCodeEditor.enterKeyLabel": "Enter",
|
||||
"kibana-react.kibanaCodeEditor.escapeKeyLabel": "Esc",
|
||||
"kibana-react.noDataPage.cantDecide.link": "詳細については、ドキュメントをご確認ください。",
|
||||
"kibana-react.noDataPage.elasticAgentCard.description": "Elasticエージェントを使用すると、シンプルで統一された方法でコンピューターからデータを収集するできます。",
|
||||
"kibana-react.noDataPage.elasticAgentCard.noPermission.description": "この統合はまだ有効ではありません。管理者にはオンにするために必要なアクセス権があります。",
|
||||
|
|
|
@ -4731,10 +4731,6 @@
|
|||
"kibana_utils.stateManagement.url.restoreUrlErrorTitle": "从 URL 还原状态时出错",
|
||||
"kibana_utils.stateManagement.url.saveStateInUrlErrorTitle": "在 URL 中保存状态时出错",
|
||||
"kibana-react.dualRangeControl.outsideOfRangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内",
|
||||
"kibana-react.kibanaCodeEditor.startEditing": "按 {key} 键开始编辑。",
|
||||
"kibana-react.kibanaCodeEditor.startEditingReadOnly": "按 {key} 键开始与代码互动。",
|
||||
"kibana-react.kibanaCodeEditor.stopEditing": "按 {key} 键停止编辑。",
|
||||
"kibana-react.kibanaCodeEditor.stopEditingReadOnly": "按 {key} 键停止与代码互动。",
|
||||
"kibana-react.noDataPage.cantDecide": "不知道使用哪一个?{link}",
|
||||
"kibana-react.noDataPage.intro": "添加您的数据以开始,或{link}{solution}。",
|
||||
"kibana-react.noDataPage.welcomeTitle": "欢迎使用 Elastic {solution}!",
|
||||
|
@ -4746,9 +4742,6 @@
|
|||
"kibana-react.kbnOverviewPageHeader.addIntegrationsButtonLabel": "添加集成",
|
||||
"kibana-react.kbnOverviewPageHeader.devToolsButtonLabel": "开发工具",
|
||||
"kibana-react.kbnOverviewPageHeader.stackManagementButtonLabel": "管理",
|
||||
"kibana-react.kibanaCodeEditor.ariaLabel": "代码编辑器",
|
||||
"kibana-react.kibanaCodeEditor.enterKeyLabel": "Enter",
|
||||
"kibana-react.kibanaCodeEditor.escapeKeyLabel": "Esc",
|
||||
"kibana-react.noDataPage.cantDecide.link": "请参阅我们的文档以了解更多信息。",
|
||||
"kibana-react.noDataPage.elasticAgentCard.description": "使用 Elastic 代理以简单统一的方式从您的计算机中收集数据。",
|
||||
"kibana-react.noDataPage.elasticAgentCard.noPermission.description": "尚未启用此集成。您的管理员具有打开它所需的权限。",
|
||||
|
|
|
@ -30,7 +30,7 @@ export function MachineLearningJobWizardAdvancedProvider(
|
|||
},
|
||||
|
||||
async assertDatafeedQueryEditorExists() {
|
||||
await testSubjects.existOrFail('mlAdvancedDatafeedQueryEditor > codeEditorHint');
|
||||
await testSubjects.existOrFail('mlAdvancedDatafeedQueryEditor > ~codeEditorHint');
|
||||
},
|
||||
|
||||
async assertDatafeedQueryEditorValue(expectedValue: string) {
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3204,15 +3204,7 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/code-editor-mocks@link:packages/shared-ux/code_editor/mocks":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/code-editor-types@link:packages/shared-ux/code_editor/types":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/code-editor@link:packages/shared-ux/code_editor/impl":
|
||||
"@kbn/code-editor@link:packages/shared-ux/code_editor":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue