Move painless lang support to @kbn/monaco (#81010) (#81473)

This commit is contained in:
Alison Goryachev 2020-10-22 10:06:30 -04:00 committed by GitHub
parent 429c0708db
commit c05f4bc115
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 237 additions and 339 deletions

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const ID = 'esql';

View file

@ -0,0 +1,23 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ID } from './constants';
import { lexerRules } from './lexer_rules';
export const EsqlLang = { ID, lexerRules };

View file

@ -17,9 +17,7 @@
* under the License.
*/
import { monaco } from '../../monaco';
export const ID = 'esql';
import { monaco } from '../../monaco_imports';
const brackets = [
{ open: '[', close: ']', token: 'delimiter.square' },

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { lexerRules } from './esql';

View file

@ -17,8 +17,12 @@
* under the License.
*/
export { monaco } from './monaco';
// global setup for supported languages
import './register_globals';
export { monaco } from './monaco_imports';
export { XJsonLang } from './xjson';
export { PainlessLang } from './painless';
/* eslint-disable-next-line @kbn/eslint/module_migration */
import * as BarePluginApi from 'monaco-editor/esm/vs/editor/editor.api';

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export const ID = 'painless';

View file

@ -0,0 +1,23 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ID } from './constants';
import { lexerRules } from './lexer_rules';
export const PainlessLang = { ID, lexerRules };

View file

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { lexerRules } from './painless';

View file

@ -17,16 +17,9 @@
* under the License.
*/
import { monaco } from '../../monaco';
import { monaco } from '../../monaco_imports';
export const ID = 'painless';
/**
* Extends the default type for a Monarch language so we can use
* attribute references (like @keywords to reference the keywords list)
* in the defined tokenizer
*/
interface Language extends monaco.languages.IMonarchLanguage {
export interface Language extends monaco.languages.IMonarchLanguage {
default: string;
brackets: any;
keywords: string[];
@ -41,8 +34,7 @@ interface Language extends monaco.languages.IMonarchLanguage {
}
export const lexerRules = {
default: 'invalid',
tokenPostfix: '',
default: '',
// painless does not use < >, so we define our own
brackets: [
['{', '}', 'delimiter.curly'],
@ -136,9 +128,9 @@ export const lexerRules = {
},
],
// whitespace
[/[ \t\r\n]+/, { token: 'whitespace' }],
[/[ \t\r\n]+/, '@whitespace'],
// comments
[/\/\*/, 'comment', '@comment'],
// [/\/\*/, 'comment', '@comment'],
[/\/\/.*$/, 'comment'],
// brackets
[/[{}()\[\]]/, '@brackets'],
@ -168,7 +160,6 @@ export const lexerRules = {
// strings single quoted
[/'([^'\\]|\\.)*$/, 'string.invalid'], // string without termination
[/'/, 'string', '@string_sq'],
[/"""/, { token: 'punctuation.end_triple_quote', nextEmbedded: '@pop' }],
],
comment: [
[/[^\/*]+/, 'comment'],
@ -189,6 +180,3 @@ export const lexerRules = {
],
},
} as Language;
monaco.languages.register({ id: ID });
monaco.languages.setMonarchTokensProvider(ID, lexerRules);

View file

@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { XJsonLang } from './xjson';
import { PainlessLang } from './painless';
import { EsqlLang } from './esql';
import { monaco } from './monaco_imports';
// @ts-ignore
import xJsonWorkerSrc from '!!raw-loader!../target/public/xjson.editor.worker.js';
// @ts-ignore
import defaultWorkerSrc from '!!raw-loader!../target/public/default.editor.worker.js';
/**
* Register languages and lexer rules
*/
monaco.languages.register({ id: XJsonLang.ID });
monaco.languages.setMonarchTokensProvider(XJsonLang.ID, XJsonLang.lexerRules);
monaco.languages.setLanguageConfiguration(XJsonLang.ID, XJsonLang.languageConfiguration);
monaco.languages.register({ id: PainlessLang.ID });
monaco.languages.setMonarchTokensProvider(PainlessLang.ID, PainlessLang.lexerRules);
monaco.languages.register({ id: EsqlLang.ID });
monaco.languages.setMonarchTokensProvider(EsqlLang.ID, EsqlLang.lexerRules);
/**
* Create web workers by language ID
*/
const mapLanguageIdToWorker: { [key: string]: any } = {
[XJsonLang.ID]: xJsonWorkerSrc,
};
// @ts-ignore
window.MonacoEnvironment = {
getWorker: (module: string, languageId: string) => {
const workerSrc = mapLanguageIdToWorker[languageId] || defaultWorkerSrc;
const blob = new Blob([workerSrc], { type: 'application/javascript' });
return new Worker(URL.createObjectURL(blob));
},
};

View file

@ -22,5 +22,6 @@
*/
import './language';
import { ID } from './constants';
import { lexerRules, languageConfiguration } from './lexer_rules';
export const XJsonLang = { ID };
export const XJsonLang = { ID, lexerRules, languageConfiguration };

View file

@ -19,32 +19,12 @@
// This file contains a lot of single setup logic for registering a language globally
import { monaco } from '../monaco';
import { monaco } from '../monaco_imports';
import { WorkerProxyService } from './worker_proxy_service';
import { registerLexerRules } from './lexer_rules';
import { ID } from './constants';
// @ts-ignore
import workerSrc from '!!raw-loader!../../target/public/xjson.editor.worker.js';
const wps = new WorkerProxyService();
// Register rules against shared monaco instance.
registerLexerRules(monaco);
// In future we will need to make this map languages to workers using "id" and/or "label" values
// that get passed in. Also this should not live inside the "xjson" dir directly. We can update this
// once we have another worker.
// @ts-ignore
window.MonacoEnvironment = {
getWorker: (module: string, languageId: string) => {
if (languageId === ID) {
// In kibana we will probably build this once and then load with raw-loader
const blob = new Blob([workerSrc], { type: 'application/javascript' });
return new Worker(URL.createObjectURL(blob));
}
},
};
monaco.languages.onLanguage(ID, async () => {
return wps.setup();
});

View file

@ -17,17 +17,4 @@
* under the License.
*/
/* eslint-disable-next-line @kbn/eslint/module_migration */
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import * as xJson from './xjson';
import * as esql from './esql';
import * as painless from './painless';
export const registerLexerRules = (m: typeof monaco) => {
m.languages.register({ id: xJson.ID });
m.languages.setMonarchTokensProvider(xJson.ID, xJson.lexerRules);
m.languages.register({ id: painless.ID });
m.languages.setMonarchTokensProvider(painless.ID, painless.lexerRules);
m.languages.register({ id: esql.ID });
m.languages.setMonarchTokensProvider(esql.ID, esql.lexerRules);
};
export { lexerRules, languageConfiguration } from './xjson';

View file

@ -17,15 +17,10 @@
* under the License.
*/
import { monaco } from '../../monaco';
import { ID } from '../constants';
import './painless';
import './esql';
import { monaco } from '../../monaco_imports';
import { globals } from './shared';
export { ID };
export const lexerRules: monaco.languages.IMonarchLanguage = {
...(globals as any),
@ -124,11 +119,7 @@ export const lexerRules: monaco.languages.IMonarchLanguage = {
},
};
monaco.languages.register({
id: ID,
});
monaco.languages.setMonarchTokensProvider(ID, lexerRules);
monaco.languages.setLanguageConfiguration(ID, {
export const languageConfiguration: monaco.languages.LanguageConfiguration = {
brackets: [
['{', '}'],
['[', ']'],
@ -138,4 +129,4 @@ monaco.languages.setLanguageConfiguration(ID, {
{ open: '[', close: ']' },
{ open: '"', close: '"' },
],
});
};

View file

@ -18,7 +18,7 @@
*/
import { ParseResult } from './grammar';
import { monaco } from '../monaco';
import { monaco } from '../monaco_imports';
import { XJsonWorker } from './worker';
import { ID } from './constants';

View file

@ -19,33 +19,40 @@
const path = require('path');
const createLangWorkerConfig = (lang) => ({
mode: 'production',
entry: path.resolve(__dirname, 'src', lang, 'worker', `${lang}.worker.ts`),
output: {
path: path.resolve(__dirname, 'target/public'),
filename: `${lang}.editor.worker.js`,
},
resolve: {
modules: ['node_modules'],
extensions: ['.js', '.ts', '.tsx'],
},
stats: 'errors-only',
module: {
rules: [
{
test: /\.(js|ts)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
babelrc: false,
presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
const createLangWorkerConfig = (lang) => {
const entry =
lang === 'default'
? 'monaco-editor/esm/vs/editor/editor.worker.js'
: path.resolve(__dirname, 'src', lang, 'worker', `${lang}.worker.ts`);
return {
mode: 'production',
entry,
output: {
path: path.resolve(__dirname, 'target/public'),
filename: `${lang}.editor.worker.js`,
},
resolve: {
modules: ['node_modules'],
extensions: ['.js', '.ts', '.tsx'],
},
stats: 'errors-only',
module: {
rules: [
{
test: /\.(js|ts)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
babelrc: false,
presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
},
},
},
},
],
},
});
],
},
};
};
module.exports = [createLangWorkerConfig('xjson')];
module.exports = [createLangWorkerConfig('xjson'), createLangWorkerConfig('default')];

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { PainlessLang } from '@kbn/monaco';
import { CodeEditor } from '../../../../../../src/plugins/kibana_react/public';
interface Props {
@ -14,7 +15,7 @@ interface Props {
export function Editor({ code, onChange }: Props) {
return (
<CodeEditor
languageId="painless"
languageId={PainlessLang.ID}
// 99% width allows the editor to resize horizontally. 100% prevents it from resizing.
width="99%"
height="100%"

View file

@ -1,7 +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;
* you may not use this file except in compliance with the Elastic License.
*/
export { monacoPainlessLang } from './monaco_painless_lang';

View file

@ -1,174 +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;
* you may not use this file except in compliance with the Elastic License.
*/
import { monaco } from '@kbn/monaco';
/**
* Extends the default type for a Monarch language so we can use
* attribute references (like @keywords to reference the keywords list)
* in the defined tokenizer
*/
interface Language extends monaco.languages.IMonarchLanguage {
default: string;
brackets: any;
keywords: string[];
symbols: RegExp;
escapes: RegExp;
digits: RegExp;
primitives: string[];
octaldigits: RegExp;
binarydigits: RegExp;
constants: string[];
operators: string[];
}
export const monacoPainlessLang = {
default: '',
// painless does not use < >, so we define our own
brackets: [
['{', '}', 'delimiter.curly'],
['[', ']', 'delimiter.square'],
['(', ')', 'delimiter.parenthesis'],
],
keywords: [
'if',
'in',
'else',
'while',
'do',
'for',
'continue',
'break',
'return',
'new',
'try',
'catch',
'throw',
'this',
'instanceof',
],
primitives: ['void', 'boolean', 'byte', 'short', 'char', 'int', 'long', 'float', 'double', 'def'],
constants: ['true', 'false'],
operators: [
'=',
'>',
'<',
'!',
'~',
'?',
'?:',
'?.',
':',
'==',
'===',
'<=',
'>=',
'!=',
'!==',
'&&',
'||',
'++',
'--',
'+',
'-',
'*',
'/',
'&',
'|',
'^',
'%',
'<<',
'>>',
'>>>',
'+=',
'-=',
'*=',
'/=',
'&=',
'|=',
'^=',
'%=',
'<<=',
'>>=',
'>>>=',
'->',
'::',
'=~',
'==~',
],
symbols: /[=><!~?:&|+\-*\/^%]+/,
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
digits: /\d+(_+\d+)*/,
octaldigits: /[0-7]+(_+[0-7]+)*/,
binarydigits: /[0-1]+(_+[0-1]+)*/,
hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
tokenizer: {
root: [
// identifiers and keywords
[
/[a-zA-Z_][\w]*/,
{
cases: {
'@keywords': 'keyword',
'@primitives': 'type',
'@constants': 'constant',
'@default': 'identifier',
},
},
],
// whitespace
[/[ \t\r\n]+/, '@whitespace'],
// comments
// [/\/\*/, 'comment', '@comment'],
[/\/\/.*$/, 'comment'],
// brackets
[/[{}()\[\]]/, '@brackets'],
// operators
[
/@symbols/,
{
cases: {
'@operators': 'operators',
'@default': '',
},
},
],
// numbers
[/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/, 'number.float'],
[/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/, 'number.float'],
[/0[xX](@hexdigits)[Ll]?/, 'number.hex'],
[/0(@octaldigits)[Ll]?/, 'number.octal'],
[/0[bB](@binarydigits)[Ll]?/, 'number.binary'],
[/(@digits)[fFdD]/, 'number.float'],
[/(@digits)[lL]?/, 'number'],
// delimiter: after numbers due to conflict with decimals and dot
[/[;,.]/, 'delimiter'],
// strings double quoted
[/"([^"\\]|\\.)*$/, 'string.invalid'], // string without termination
[/"/, 'string', '@string_dq'],
// strings single quoted
[/'([^'\\]|\\.)*$/, 'string.invalid'], // string without termination
[/'/, 'string', '@string_sq'],
],
comment: [
[/[^\/*]+/, 'comment'],
[/\*\//, 'comment', '@pop'],
[/[\/*]/, 'comment'],
],
string_dq: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, 'string', '@pop'],
],
string_sq: [
[/[^\\']+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/'/, 'string', '@pop'],
],
},
} as Language;

View file

@ -16,7 +16,6 @@ import { PLUGIN } from '../common/constants';
import { PluginDependencies } from './types';
import { getLinks } from './links';
import { LanguageService } from './services';
import { ILicense } from '../../licensing/common/types';
const checkLicenseStatus = (license: ILicense) => {
@ -25,8 +24,6 @@ const checkLicenseStatus = (license: ILicense) => {
};
export class PainlessLabUIPlugin implements Plugin<void, void, PluginDependencies> {
languageService = new LanguageService();
public setup(
{ http, getStartServices, uiSettings }: CoreSetup,
{ devTools, home, licensing }: PluginDependencies
@ -80,8 +77,6 @@ export class PainlessLabUIPlugin implements Plugin<void, void, PluginDependencie
chrome,
} = core;
this.languageService.setup();
const license = await licensing.license$.pipe(first()).toPromise();
const licenseStatus = checkLicenseStatus(license);
@ -117,7 +112,5 @@ export class PainlessLabUIPlugin implements Plugin<void, void, PluginDependencie
public start() {}
public stop() {
this.languageService.stop();
}
public stop() {}
}

View file

@ -1,7 +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;
* you may not use this file except in compliance with the Elastic License.
*/
export { LanguageService } from './language_service';

View file

@ -1,45 +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;
* you may not use this file except in compliance with the Elastic License.
*/
// It is important that we use this specific monaco instance so that
// editor settings are registered against the instance our React component
// uses.
import { monaco } from '@kbn/monaco';
// @ts-ignore
import workerSrc from 'raw-loader!monaco-editor/min/vs/base/worker/workerMain.js';
import { monacoPainlessLang } from '../lib';
const LANGUAGE_ID = 'painless';
// Safely check whether these globals are present
const CAN_CREATE_WORKER = typeof Blob === 'function' && typeof Worker === 'function';
export class LanguageService {
private originalMonacoEnvironment: any;
public setup() {
monaco.languages.register({ id: LANGUAGE_ID });
monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, monacoPainlessLang);
if (CAN_CREATE_WORKER) {
this.originalMonacoEnvironment = (window as any).MonacoEnvironment;
(window as any).MonacoEnvironment = {
getWorker: () => {
const blob = new Blob([workerSrc], { type: 'application/javascript' });
return new Worker(window.URL.createObjectURL(blob));
},
};
}
}
public stop() {
if (CAN_CREATE_WORKER) {
(window as any).MonacoEnvironment = this.originalMonacoEnvironment;
}
}
}