From f0e31b8f0ca1640502e931de738b06c9f39ec870 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 16 Oct 2020 13:15:14 -0400 Subject: [PATCH] move painless monaco code to kbn/monaco package --- packages/kbn-monaco/src/global.ts | 43 + packages/kbn-monaco/src/index.ts | 4 + packages/kbn-monaco/src/painless/constants.ts | 20 + packages/kbn-monaco/src/painless/index.ts | 25 + packages/kbn-monaco/src/painless/language.ts | 39 + .../src/painless/painless_completion.ts | 96 ++ .../src/painless/painless_lexer_rules.ts | 187 +++ packages/kbn-monaco/src/painless/types.ts | 32 + .../kbn-monaco/src/painless/worker/context.ts | 1301 +++++++++++++++++ .../kbn-monaco/src/painless/worker/index.ts | 20 + .../src/painless/worker/painless.worker.ts | 32 + .../worker/painless_completion_utils.ts | 241 +++ .../src/painless/worker/painless_worker.ts | 85 ++ .../src/painless/worker_proxy_service.ts | 42 + packages/kbn-monaco/src/xjson/language.ts | 16 - .../src/xjson/lexer_rules/painless.ts | 3 - packages/kbn-monaco/webpack.config.js | 2 +- .../public/application/components/editor.tsx | 4 +- .../public/services/completion_adapter.ts | 2 +- .../public/services/language_service.ts | 47 +- 20 files changed, 2195 insertions(+), 46 deletions(-) create mode 100644 packages/kbn-monaco/src/global.ts create mode 100644 packages/kbn-monaco/src/painless/constants.ts create mode 100644 packages/kbn-monaco/src/painless/index.ts create mode 100644 packages/kbn-monaco/src/painless/language.ts create mode 100644 packages/kbn-monaco/src/painless/painless_completion.ts create mode 100644 packages/kbn-monaco/src/painless/painless_lexer_rules.ts create mode 100644 packages/kbn-monaco/src/painless/types.ts create mode 100644 packages/kbn-monaco/src/painless/worker/context.ts create mode 100644 packages/kbn-monaco/src/painless/worker/index.ts create mode 100644 packages/kbn-monaco/src/painless/worker/painless.worker.ts create mode 100644 packages/kbn-monaco/src/painless/worker/painless_completion_utils.ts create mode 100644 packages/kbn-monaco/src/painless/worker/painless_worker.ts create mode 100644 packages/kbn-monaco/src/painless/worker_proxy_service.ts diff --git a/packages/kbn-monaco/src/global.ts b/packages/kbn-monaco/src/global.ts new file mode 100644 index 0000000000000..db38180400830 --- /dev/null +++ b/packages/kbn-monaco/src/global.ts @@ -0,0 +1,43 @@ +/* + * 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 { PainlessLang } from './painless'; +import { XJsonLang } from './xjson'; +// @ts-ignore +import xJsonWorkerSrc from '!!raw-loader!../target/public/xjson.editor.worker.js'; +// @ts-ignore +import painlessWorkerSrc from '!!raw-loader!../target/public/painless.editor.worker.js'; + +const mapLanguageIdToWorker: { [key: string]: any } = { + [XJsonLang.ID]: xJsonWorkerSrc, + [PainlessLang.ID]: painlessWorkerSrc, +}; + +// @ts-ignore +window.MonacoEnvironment = { + getWorker: (module: string, languageId: string) => { + const workerSrc = mapLanguageIdToWorker[languageId]; + + if (workerSrc) { + // 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)); + } + }, +}; diff --git a/packages/kbn-monaco/src/index.ts b/packages/kbn-monaco/src/index.ts index 9213a1bfe1327..19f2f4cf6c247 100644 --- a/packages/kbn-monaco/src/index.ts +++ b/packages/kbn-monaco/src/index.ts @@ -17,8 +17,12 @@ * under the License. */ +// Creates web workers +import './global'; + export { monaco } from './monaco'; 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'; diff --git a/packages/kbn-monaco/src/painless/constants.ts b/packages/kbn-monaco/src/painless/constants.ts new file mode 100644 index 0000000000000..32bbc0aaaa0be --- /dev/null +++ b/packages/kbn-monaco/src/painless/constants.ts @@ -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'; diff --git a/packages/kbn-monaco/src/painless/index.ts b/packages/kbn-monaco/src/painless/index.ts new file mode 100644 index 0000000000000..c9a3d830a398d --- /dev/null +++ b/packages/kbn-monaco/src/painless/index.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * This import registers the painless monaco language contribution + */ +import './language'; + +export const PainlessLang = { ID: 'painless' }; diff --git a/packages/kbn-monaco/src/painless/language.ts b/packages/kbn-monaco/src/painless/language.ts new file mode 100644 index 0000000000000..1d8768960d0de --- /dev/null +++ b/packages/kbn-monaco/src/painless/language.ts @@ -0,0 +1,39 @@ +/* + * 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 { monaco } from '../monaco'; + +import { painlessLanguage } from './painless_lexer_rules'; +import { PainlessCompletionAdapter } from './painless_completion'; +import { WorkerProxyService } from './worker_proxy_service'; +import { ID } from './constants'; + +const wps = new WorkerProxyService(); + +monaco.languages.register({ id: ID }); + +const worker = (...uris: any[]) => { + return wps.getWorker(uris); +}; + +monaco.languages.onLanguage(ID, async () => { + wps.setup(); +}); +monaco.languages.setMonarchTokensProvider(ID, painlessLanguage); +monaco.languages.registerCompletionItemProvider(ID, new PainlessCompletionAdapter(worker)); diff --git a/packages/kbn-monaco/src/painless/painless_completion.ts b/packages/kbn-monaco/src/painless/painless_completion.ts new file mode 100644 index 0000000000000..a2e8a6208c722 --- /dev/null +++ b/packages/kbn-monaco/src/painless/painless_completion.ts @@ -0,0 +1,96 @@ +/* + * 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 { monaco } from '../monaco'; +import { PainlessCompletionResult, PainlessCompletionKind } from './types'; + +const getCompletionKind = (kind: PainlessCompletionKind): monaco.languages.CompletionItemKind => { + const monacoItemKind = monaco.languages.CompletionItemKind; + + switch (kind) { + case 'type': + return monacoItemKind.Interface; + case 'class': + return monacoItemKind.Class; + case 'method': + return monacoItemKind.Method; + case 'constructor': + return monacoItemKind.Constructor; + case 'property': + return monacoItemKind.Property; + } + return monacoItemKind.Property; +}; + +export class PainlessCompletionAdapter implements monaco.languages.CompletionItemProvider { + // @ts-ignore + constructor(private _worker) {} + + public get triggerCharacters(): string[] { + return ['.']; + } + + provideCompletionItems( + model: monaco.editor.IReadOnlyModel, + position: monaco.Position, + context: monaco.languages.CompletionContext, + token: monaco.CancellationToken + ): Promise { + const resource = model.uri; + + // Active line characters + const currentLineChars = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 0, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + + return this._worker(resource) + .then((worker: any) => { + return worker.provideAutocompleteSuggestions(resource.toString(), currentLineChars); + }) + .then((completionInfo: PainlessCompletionResult) => { + const wordInfo = model.getWordUntilPosition(position); + const wordRange = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: wordInfo.startColumn, + endColumn: wordInfo.endColumn, + }; + + const suggestions = completionInfo.suggestions.map( + ({ label, insertText, documentation, kind }) => { + return { + label, + insertText, + documentation, + range: wordRange, + kind: getCompletionKind(kind), + }; + } + ); + + return { + isIncomplete: completionInfo.isIncomplete, + suggestions, + }; + }); + } +} diff --git a/packages/kbn-monaco/src/painless/painless_lexer_rules.ts b/packages/kbn-monaco/src/painless/painless_lexer_rules.ts new file mode 100644 index 0000000000000..7193bbae7e9cb --- /dev/null +++ b/packages/kbn-monaco/src/painless/painless_lexer_rules.ts @@ -0,0 +1,187 @@ +/* + * 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 { monaco } from '../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 painlessLanguage = { + 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: /[=> { + worker.initialize((ctx: any, createData: any) => { + return new PainlessWorker(ctx); + }); +}; diff --git a/packages/kbn-monaco/src/painless/worker/painless_completion_utils.ts b/packages/kbn-monaco/src/painless/worker/painless_completion_utils.ts new file mode 100644 index 0000000000000..79d95db1955be --- /dev/null +++ b/packages/kbn-monaco/src/painless/worker/painless_completion_utils.ts @@ -0,0 +1,241 @@ +/* + * 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 { PainlessCompletionResult, PainlessCompletionItem } from '../types'; +import { context } from './context'; + +interface Field { + name: string; + type: string; +} + +interface Method { + name: string; + parameters: string[]; + return: string; +} + +interface Constructor { + declaring: string; + parameters: string[]; +} + +interface ContextClass { + name: string; + imported: boolean; + constructors: Constructor[]; + static_methods: Method[]; + methods: Method[]; + static_fields: Field[]; + fields: Field[]; +} + +interface ClassNameMap { + [key: string]: ContextClass; +} + +const createClassNameMap = () => { + return context.classes.reduce((acc: ClassNameMap, currentVal) => { + const className = currentVal.name.split('.').pop(); + + if (className) { + acc[className] = currentVal; + } + + return acc; + }, {}); +}; + +const getTypes = (): string[] => { + return context.classes + .filter( + ({ + static_fields: staticFields, + fields, + static_methods: staticMethods, + methods, + constructors, + }) => { + if ( + staticMethods.length === 0 && + methods.length === 0 && + staticFields.length === 0 && + fields.length === 0 && + constructors.length === 0 + ) { + return true; + } + } + ) + .map((type) => type.name); +}; + +export const painlessTypes = getTypes(); + +export const getPainlessClassesToAutocomplete = (): PainlessCompletionResult => { + const painlessClasses: PainlessCompletionItem[] = context.classes.map(({ name }) => { + const className = name.split('.').pop() || name; // TODO ES to add "displayName" field so this won't be necessary + const isType = painlessTypes.includes(name); + + return { + label: className, + kind: isType ? 'type' : 'class', + documentation: `Class ${className}`, + insertText: className, + }; + }); + return { + isIncomplete: false, + suggestions: painlessClasses, + }; +}; + +// TODO making the assumption there will never be >5 parameters +const indexToLetterMap: { + [key: number]: string; +} = { + 0: 'a', + 1: 'b', + 2: 'c', + 3: 'd', + 4: 'e', + 5: 'f', +}; + +// TODO for now assuming we will always have parameters and return value +const getMethodDescription = ( + methodName: string, + parameters: string[], + returnValue: string +): string => { + const parameterDescription: string = parameters.reduce( + (description: string, parameterType: string, index: number) => { + const newParameterDescription = `${parameterType} ${indexToLetterMap[index]}`; + const isLastParameter = parameters.length - 1 === index; + + description = `${description}${newParameterDescription}${isLastParameter ? '' : ', '}`; + + return description; + }, + '' + ); + + // Final format will look something like this: + // pow(double a, double b): double + return `${methodName}(${parameterDescription}): ${returnValue}`; +}; + +export const getPainlessConstructorsToAutocomplete = (): PainlessCompletionResult => { + const painlessConstructors = context.classes + .filter(({ constructors }) => constructors.length > 0) + .map(({ constructors }) => constructors) + .flat(); + + const constructors: PainlessCompletionItem[] = painlessConstructors + // There are sometimes multiple definitions for the same constructor + // This method filters them out so we don't display more than once in autocomplete + .filter((constructor, index, constructorArray) => { + return ( + constructorArray.findIndex(({ declaring }) => declaring === constructor.declaring) === index + ); + }) + .map(({ declaring }) => { + const constructorName = declaring.split('.').pop() || declaring; // TODO ES to add "displayName" field so this won't be necessary + + return { + label: constructorName, + kind: 'constructor', + documentation: `Constructor ${constructorName}`, + insertText: constructorName, + }; + }); + + return { + isIncomplete: false, + suggestions: constructors, + }; +}; + +export const getPainlessClassToAutocomplete = (className: string): PainlessCompletionResult => { + const classNameMap = createClassNameMap(); + + if (!classNameMap[className]) { + return { + isIncomplete: false, + suggestions: [], + }; + } + + const { + static_fields: staticFields, + fields, + static_methods: staticMethods, + methods, + } = classNameMap[className]; + + const staticFieldsAutocomplete: PainlessCompletionItem[] = staticFields.map(({ name, type }) => { + return { + label: name, + kind: 'property', + documentation: `${name}: ${type}`, + insertText: name, + }; + }); + + const fieldsAutocomplete: PainlessCompletionItem[] = fields.map(({ name, type }) => { + return { + label: name, + kind: 'property', + documentation: `${name}: ${type}`, + insertText: name, + }; + }); + + const staticMethodsAutocomplete: PainlessCompletionItem[] = staticMethods.map( + ({ name, parameters, return: returnValue }) => { + return { + label: name, + kind: 'method', + documentation: getMethodDescription(name, parameters, returnValue), + insertText: name, + }; + } + ); + + const methodsAutocomplete: PainlessCompletionItem[] = methods.map( + ({ name, parameters, return: returnValue }) => { + return { + label: name, + kind: 'method', + documentation: getMethodDescription(name, parameters, returnValue), + insertText: name, + }; + } + ); + + return { + isIncomplete: false, + suggestions: [ + ...staticFieldsAutocomplete, + ...staticMethodsAutocomplete, + ...methodsAutocomplete, + ...fieldsAutocomplete, + ], + }; +}; diff --git a/packages/kbn-monaco/src/painless/worker/painless_worker.ts b/packages/kbn-monaco/src/painless/worker/painless_worker.ts new file mode 100644 index 0000000000000..1860c526a0d6c --- /dev/null +++ b/packages/kbn-monaco/src/painless/worker/painless_worker.ts @@ -0,0 +1,85 @@ +/* + * 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. + */ + +/* eslint-disable-next-line @kbn/eslint/module_migration */ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +import { PainlessCompletionResult } from '../types'; + +import { + getPainlessClassToAutocomplete, + painlessTypes, + getPainlessClassesToAutocomplete, + getPainlessConstructorsToAutocomplete, +} from './painless_completion_utils'; + +export class PainlessWorker { + constructor(private ctx: monaco.worker.IWorkerContext) {} + + private _getModel(modelUri: string) { + return this.ctx.getMirrorModels().find((m) => m.uri.toString() === modelUri); + } + + async provideAutocompleteSuggestions( + uri: string, + currentLineChars: any + ): Promise { + const model = this._getModel(uri); + + if (!model) { + return Promise.resolve({ + isIncomplete: false, + suggestions: [], + }); + } + + // const currentText = model.getValue(); + + // Array of the active line words, e.g., [boolean, isInCircle] + const words = currentLineChars.replace('\t', '').split(' '); + // What the user is currently typing + const activeTyping = words[words.length - 1]; + + // If the active typing contains dot notation, we assume we need to access the object's properties + const isProperty = activeTyping.split('.').length === 2; + // If the preceding word is a type, e.g., "boolean", we assume the user is declaring a variable and skip autocomplete + const hasDeclaredType = words.length === 2 && painlessTypes.includes(words[0]); + // If the preceding word contains the "new" keyword, we only provide constructor autcompletion + const isConstructor = words[words.length - 2] === 'new'; + + let autocompleteSuggestions: PainlessCompletionResult = { + isIncomplete: false, + suggestions: [], + }; + + if (isConstructor) { + autocompleteSuggestions = getPainlessConstructorsToAutocomplete(); + } else if (isProperty) { + const className = activeTyping.substring(0, activeTyping.length - 1).split('.')[0]; + + autocompleteSuggestions = getPainlessClassToAutocomplete(className); + } else { + if (!hasDeclaredType) { + autocompleteSuggestions = getPainlessClassesToAutocomplete(); + } + } + + return Promise.resolve(autocompleteSuggestions); + } +} diff --git a/packages/kbn-monaco/src/painless/worker_proxy_service.ts b/packages/kbn-monaco/src/painless/worker_proxy_service.ts new file mode 100644 index 0000000000000..c93259da38ca3 --- /dev/null +++ b/packages/kbn-monaco/src/painless/worker_proxy_service.ts @@ -0,0 +1,42 @@ +/* + * 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 { monaco } from '../monaco'; +import { PainlessWorker } from './worker'; + +export class WorkerProxyService { + private worker: monaco.editor.MonacoWebWorker | undefined; + + public async getWorker(resources: any[]) { + if (!this.worker) { + throw new Error('Worker Proxy Service has not been setup!'); + } + await this.worker.withSyncedResources(resources); + const proxy = await this.worker.getProxy(); + return proxy; + } + + public setup() { + this.worker = monaco.editor.createWebWorker({ label: 'painless', moduleId: '' }); + } + + public stop() { + if (this.worker) this.worker.dispose(); + } +} diff --git a/packages/kbn-monaco/src/xjson/language.ts b/packages/kbn-monaco/src/xjson/language.ts index 4ae7f2402ed2f..a601e0ed3eeb3 100644 --- a/packages/kbn-monaco/src/xjson/language.ts +++ b/packages/kbn-monaco/src/xjson/language.ts @@ -23,28 +23,12 @@ import { monaco } from '../monaco'; 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(); }); diff --git a/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts b/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts index 676eb3134026a..bcc99cbdfe384 100644 --- a/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts +++ b/packages/kbn-monaco/src/xjson/lexer_rules/painless.ts @@ -189,6 +189,3 @@ export const lexerRules = { ], }, } as Language; - -monaco.languages.register({ id: ID }); -monaco.languages.setMonarchTokensProvider(ID, lexerRules); diff --git a/packages/kbn-monaco/webpack.config.js b/packages/kbn-monaco/webpack.config.js index 1a7d8c031670c..ec6b707a92387 100644 --- a/packages/kbn-monaco/webpack.config.js +++ b/packages/kbn-monaco/webpack.config.js @@ -48,4 +48,4 @@ const createLangWorkerConfig = (lang) => ({ }, }); -module.exports = [createLangWorkerConfig('xjson')]; +module.exports = [createLangWorkerConfig('xjson'), createLangWorkerConfig('painless')]; diff --git a/x-pack/plugins/painless_lab/public/application/components/editor.tsx b/x-pack/plugins/painless_lab/public/application/components/editor.tsx index b8891ce6524f5..60561329dba8f 100644 --- a/x-pack/plugins/painless_lab/public/application/components/editor.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/editor.tsx @@ -4,6 +4,8 @@ * 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 +16,7 @@ interface Props { export function Editor({ code, onChange }: Props) { return ( { - // Active line characters, e.g., "boolean isInCircle" + // Active *line* characters, e.g., "boolean isInCircle" const activeCharacters = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 0, diff --git a/x-pack/plugins/painless_lab/public/services/language_service.ts b/x-pack/plugins/painless_lab/public/services/language_service.ts index f703d3b876893..c42619444e4f5 100644 --- a/x-pack/plugins/painless_lab/public/services/language_service.ts +++ b/x-pack/plugins/painless_lab/public/services/language_service.ts @@ -7,42 +7,41 @@ // 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'; +// import { monaco } from '@kbn/monaco'; -// @ts-ignore -import workerSrc from 'raw-loader!monaco-editor/min/vs/base/worker/workerMain.js'; +// // @ts-ignore +// import workerSrc from 'raw-loader!monaco-editor/min/vs/base/worker/workerMain.js'; -import { monacoPainlessLang } from '../lib'; +// import { monacoPainlessLang } from '../lib'; -import { PainlessCompletionAdapter } from './completion_adapter'; +// import { PainlessCompletionAdapter } from './completion_adapter'; -const LANGUAGE_ID = 'painless'; +// const LANGUAGE_ID = 'painless'; // Safely check whether these globals are present -const CAN_CREATE_WORKER = typeof Blob === 'function' && typeof Worker === 'function'; +// const CAN_CREATE_WORKER = typeof Blob === 'function' && typeof Worker === 'function'; export class LanguageService { - private originalMonacoEnvironment: any; + // private originalMonacoEnvironment: any; public setup() { - monaco.languages.register({ id: LANGUAGE_ID }); - monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, monacoPainlessLang); - monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, new PainlessCompletionAdapter()); - - 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)); - }, - }; - } + // monaco.languages.register({ id: LANGUAGE_ID }); + // monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, monacoPainlessLang); + // monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, new PainlessCompletionAdapter()); + // 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; - } + // if (CAN_CREATE_WORKER) { + // (window as any).MonacoEnvironment = this.originalMonacoEnvironment; + // } } }