Skip to content

Commit

Permalink
Testing a potential solution for #620: Statemachine example uses two …
Browse files Browse the repository at this point in the history
…editors with one language server now
  • Loading branch information
kaisalmen committed May 24, 2024
1 parent d6e74b0 commit 54eb77b
Show file tree
Hide file tree
Showing 14 changed files with 120 additions and 56 deletions.
2 changes: 1 addition & 1 deletion packages/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"@codingame/monaco-vscode-typescript-language-features-default-extension": "~5.2.0",
"@typefox/monaco-editor-react": "~4.2.0",
"express": "~4.19.2",
"langium": "3.0.0",
"langium": "~3.0.0",
"monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~5.2.0",
"monaco-editor-wrapper": "~5.2.0",
"monaco-languageclient": "~8.5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,24 @@
import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override';
import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override';
import getLocalizationServiceOverride from '@codingame/monaco-vscode-localization-service-override';
import { IConnectionProvider } from 'monaco-languageclient';
import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscode/services';
import { UserConfig } from 'monaco-editor-wrapper';
import { LanguageClientConfig, UserConfig } from 'monaco-editor-wrapper';
// cannot be imported with assert as json contains comments
import statemachineLanguageConfig from './language-configuration.json?raw';
import responseStatemachineTm from '../syntaxes/statemachine.tmLanguage.json?raw';

export const createLangiumGlobalConfig = async (params: {
languageServerId: string,
useLanguageClient: boolean,
text?: string,
worker: Worker,
messagePort?: MessagePort
worker?: Worker,
messagePort?: MessagePort,
connectionProvider?: IConnectionProvider
}): Promise<UserConfig> => {
const extensionFilesOrContents = new Map<string, string | URL>();
extensionFilesOrContents.set('/statemachine-configuration.json', statemachineLanguageConfig);
extensionFilesOrContents.set('/statemachine-grammar.json', responseStatemachineTm);
extensionFilesOrContents.set(`/${params.languageServerId}-statemachine-configuration.json`, statemachineLanguageConfig);
extensionFilesOrContents.set(`/${params.languageServerId}-statemachine-grammar.json`, responseStatemachineTm);

let main;
if (params.text) {
Expand All @@ -29,6 +33,16 @@ export const createLangiumGlobalConfig = async (params: {
};
}

const languageClientConfig: LanguageClientConfig | undefined = params.useLanguageClient && params.worker ? {
languageId: 'statemachine',
options: {
$type: 'WorkerDirect',
worker: params.worker,
messagePort: params.messagePort,
},
connectionProvider: params.connectionProvider
} : undefined;

return {
wrapperConfig: {
serviceConfig: {
Expand Down Expand Up @@ -58,12 +72,12 @@ export const createLangiumGlobalConfig = async (params: {
id: 'statemachine',
extensions: ['.statemachine'],
aliases: ['statemachine', 'Statemachine'],
configuration: './statemachine-configuration.json'
configuration: `./${params.languageServerId}-statemachine-configuration.json`
}],
grammars: [{
language: 'statemachine',
scopeName: 'source.statemachine',
path: './statemachine-grammar.json'
path: `./${params.languageServerId}-statemachine-grammar.json`
}]
}
},
Expand All @@ -78,13 +92,6 @@ export const createLangiumGlobalConfig = async (params: {
}
}
},
languageClientConfig: {
languageId: 'statemachine',
options: {
$type: 'WorkerDirect',
worker: params.worker,
messagePort: params.messagePort
}
}
languageClientConfig
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
statemachine TrafficLight

events
switchCapacity
next

initialState PowerOff

state PowerOff
switchCapacity => RedLight
end

state RedLight
switchCapacity => PowerOff
next => GreenLight
end

state OrangeLight
switchCapacity => PowerOff
next => RedLight
end

state YellowLight
switchCapacity => PowerOff
next => OrangeLight
end

state GreenLight
switchCapacity => PowerOff
next => YellowLight
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/******************************************************************************
* This file was generated by langium-cli 3.0.0-next.e78aeba.
* This file was generated by langium-cli 3.0.3.
* DO NOT EDIT MANUALLY!
******************************************************************************/

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/******************************************************************************
* This file was generated by langium-cli 3.0.0-next.e78aeba.
* This file was generated by langium-cli 3.0.3.
* DO NOT EDIT MANUALLY!
******************************************************************************/

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/******************************************************************************
* This file was generated by langium-cli 3.0.0-next.e78aeba.
* This file was generated by langium-cli 3.0.3.
* DO NOT EDIT MANUALLY!
******************************************************************************/

import type { LangiumGeneratedCoreServices, LangiumGeneratedSharedCoreServices, LanguageMetaData, Module } from 'langium';
import type { LangiumSharedServices, LangiumServices } from 'langium/lsp';
import type { LangiumSharedCoreServices, LangiumCoreServices, LangiumGeneratedCoreServices, LangiumGeneratedSharedCoreServices, LanguageMetaData, Module } from 'langium';
import { StatemachineAstReflection } from './ast.js';
import { StatemachineGrammar } from './grammar.js';

Expand All @@ -14,11 +13,11 @@ export const StatemachineLanguageMetaData = {
caseInsensitive: false
} as const satisfies LanguageMetaData;

export const StatemachineGeneratedSharedModule: Module<LangiumSharedServices, LangiumGeneratedSharedCoreServices> = {
export const StatemachineGeneratedSharedModule: Module<LangiumSharedCoreServices, LangiumGeneratedSharedCoreServices> = {
AstReflection: () => new StatemachineAstReflection()
};

export const StatemachineGeneratedModule: Module<LangiumServices, LangiumGeneratedCoreServices> = {
export const StatemachineGeneratedModule: Module<LangiumCoreServices, LangiumGeneratedCoreServices> = {
Grammar: () => StatemachineGrammar(),
LanguageMetaData: () => StatemachineLanguageMetaData,
parser: {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
* Licensed under the MIT License. See LICENSE in the package root for license information.
* ------------------------------------------------------------------------------------------ */

import type { Module } from 'langium';
import type { DefaultSharedModuleContext, LangiumServices, LangiumSharedServices, PartialLangiumServices } from 'langium/lsp';
import { inject } from 'langium';
import { createDefaultModule, createDefaultSharedModule } from 'langium/lsp';
import { type Module, inject } from 'langium';
import type { LangiumServices, LangiumSharedServices, PartialLangiumServices } from 'langium/lsp';
import { createDefaultModule, createDefaultSharedModule, type DefaultSharedModuleContext } from 'langium/lsp';
import { StatemachineGeneratedModule, StatemachineGeneratedSharedModule } from './generated/module.js';
import { registerValidationChecks, StatemachineValidator } from './statemachine-validator.js';
import { StatemachineValidator, registerValidationChecks } from './statemachine-validator.js';

/**
* Declaration of custom services - add your own service classes here.
Expand Down Expand Up @@ -66,5 +65,10 @@ export function createStatemachineServices(context: DefaultSharedModuleContext):
);
shared.ServiceRegistry.register(statemachine);
registerValidationChecks(statemachine);
if (!context.connection) {
// We don't run inside a language server
// Therefore, initialize the configuration provider instantly
shared.workspace.ConfigurationProvider.initialized({});
}
return { shared, statemachine };
}
2 changes: 2 additions & 0 deletions packages/examples/src/langium/statemachine/main-react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const configureMonacoWorkers = () => {
export const runStatemachineReact = async () => {
try {
const langiumGlobalConfig = await createLangiumGlobalConfig({
languageServerId: 'react',
useLanguageClient: true,
text,
worker: loadStatemachineWorkerRegular()
});
Expand Down
51 changes: 32 additions & 19 deletions packages/examples/src/langium/statemachine/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import * as vscode from 'vscode';
import { createModelReference } from 'vscode/monaco';
import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper';
import { useWorkerFactory } from 'monaco-editor-wrapper/workerFactory';
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser.js';
import { createLangiumGlobalConfig } from './config/wrapperStatemachineConfig.js';
import workerUrl from './worker/statemachine-server?worker&url';
import workerPortUrl from './worker/statemachine-server-port?worker&url';
import text from './content/example.statemachine?raw';
import textMod from './content/example-mod.statemachine?raw';

const wrapper = new MonacoEditorLanguageClientWrapper();
const wrapper2 = new MonacoEditorLanguageClientWrapper();
Expand All @@ -19,7 +21,7 @@ export const configureMonacoWorkers = () => {
useWorkerFactory({
ignoreMapping: true,
workerLoaders: {
editorWorkerService: () => new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url), { type: 'module' }),
editorWorkerService: () => new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker.js', import.meta.url), { type: 'module' })
}
});
};
Expand All @@ -30,37 +32,48 @@ const startEditor = async () => {
return;
}

// init first worker regularly
const stateMachineWorkerRegular = loadStatemachineWorkerRegular();
// init worker with port for client and worker
const stateMachineWorkerPort = loadStatemachinWorkerPort();
// use callback to receive message back from worker independent of the message channel the LSP is using
stateMachineWorkerPort.onmessage = (event) => {
console.log('Received message from worker: ' + event.data);
};
const channel = new MessageChannel();
stateMachineWorkerPort.postMessage({
port: channel.port2
}, [channel.port2]);

const reader = new BrowserMessageReader(channel.port1);
const writer = new BrowserMessageWriter(channel.port1);
reader.listen((message) => {
console.log('Received message from worker:', message);
});

// the configuration does not contain any text content
const langiumGlobalConfig = await createLangiumGlobalConfig({
worker: stateMachineWorkerRegular
languageServerId: 'first',
useLanguageClient: true,
worker: stateMachineWorkerPort,
messagePort: channel.port1,
connectionProvider: {
get: async () => ({ reader, writer })
}
});
await wrapper.initAndStart(langiumGlobalConfig, document.getElementById('monaco-editor-root'));

// here the modelReference is created manually and given to the updateEditorModels of the wrapper
const uri = vscode.Uri.parse('/workspace/statemachineUri.statemachine');
const uri = vscode.Uri.parse('/workspace/statemachine-mod.statemachine');
const modelRef = await createModelReference(uri, text);
wrapper.updateEditorModels({
modelRef
});

// init second worker with port for client and worker
const stateMachineWorkerPort = loadStatemachinWorkerPort();
// use callback to receive message back from worker independent of the message channel the LSP is using
stateMachineWorkerPort.onmessage = (event) => {
console.log('Received message from worker: ' + event.data);
};
const channel = new MessageChannel();
stateMachineWorkerPort.postMessage({
port: channel.port2
}, [channel.port2]);

// start the second wrapper without any languageclient config
// => they share the language server and both text contents have different uris
const langiumGlobalConfig2 = await createLangiumGlobalConfig({
text,
worker: stateMachineWorkerPort,
messagePort: channel.port1
languageServerId: 'second',
useLanguageClient: false,
text: textMod
});
await wrapper2.initAndStart(langiumGlobalConfig2, document.getElementById('monaco-editor-root2'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@

/// <reference lib="WebWorker" />

import { start } from './statemachine-server-start.js';
import { start, messageReader } from './statemachine-server-start.js';

declare const self: DedicatedWorkerGlobalScope;

self.onmessage = async (event: MessageEvent) => {
const data = event.data;
console.log(event.data);
if (data.port) {
start(data.port, 'statemachine-server-port');

messageReader?.listen((message) => {
console.log('Received message from main thread:', message);
});

setTimeout(() => {
// test independent communication
self.postMessage('started');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import { startLanguageServer } from 'langium/lsp';
import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser.js';
import { createStatemachineServices } from '../ls/statemachine-module.js';

export let messageReader: BrowserMessageReader | undefined;
export let messageWriter: BrowserMessageWriter | undefined;

export const start = (port: MessagePort | DedicatedWorkerGlobalScope, name: string) => {
console.log(`Starting ${name}...`);
/* browser specific setup code */
const messageReader = new BrowserMessageReader(port);
const messageWriter = new BrowserMessageWriter(port);
messageReader = new BrowserMessageReader(port);
messageWriter = new BrowserMessageWriter(port);

const connection = createConnection(messageReader, messageWriter);

Expand Down
4 changes: 2 additions & 2 deletions packages/wrapper/src/editorAppExtended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { EditorAppBase, EditorAppConfigBase } from './editorAppBase.js';
import { registerExtension, IExtensionManifest, ExtensionHostKind } from 'vscode/extensions';
import { Logger } from 'monaco-languageclient/tools';
import { UserConfig } from './userConfig.js';
import { verifyUrlorCreateDataUrl, ModelUpdateType, isEqual, isModelUpdateRequired } from './utils.js';
import { verifyUrlOrCreateDataUrl, ModelUpdateType, isEqual, isModelUpdateRequired } from './utils.js';

export type ExtensionConfig = {
config: IExtensionManifest | object;
Expand Down Expand Up @@ -90,7 +90,7 @@ export class EditorAppExtended extends EditorAppBase {
this.extensionRegisterResults.set(manifest.name, extRegResult);
if (extensionConfig.filesOrContents && Object.hasOwn(extRegResult, 'registerFileUrl')) {
for (const entry of extensionConfig.filesOrContents) {
(extRegResult as RegisterLocalExtensionResult).registerFileUrl(entry[0], verifyUrlorCreateDataUrl(entry[1]));
(extRegResult as RegisterLocalExtensionResult).registerFileUrl(entry[0], verifyUrlOrCreateDataUrl(entry[1]));
}
}
allPromises.push(extRegResult.whenReady());
Expand Down
2 changes: 1 addition & 1 deletion packages/wrapper/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const createUrl = (config: WebSocketConfigOptions | WebSocketConfigOption
return buildUrl;
};

export const verifyUrlorCreateDataUrl = (input: string | URL) => {
export const verifyUrlOrCreateDataUrl = (input: string | URL) => {
return (input instanceof URL) ? input.href : new URL(`data:text/plain;base64,${btoa(input)}`).href;
};

Expand Down
6 changes: 3 additions & 3 deletions packages/wrapper/test/editorAppExtended.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@
* ------------------------------------------------------------------------------------------ */

import { describe, expect, test } from 'vitest';
import { EditorAppConfigExtended, EditorAppExtended, verifyUrlorCreateDataUrl } from 'monaco-editor-wrapper';
import { EditorAppConfigExtended, EditorAppExtended, verifyUrlOrCreateDataUrl } from 'monaco-editor-wrapper';
import { createBaseConfig, createEditorAppConfig } from './helper.js';

describe('Test EditorAppExtended', () => {

test('verifyUrlorCreateDataUrl: url', () => {
const url = new URL('./editorAppExtended.test.ts', import.meta.url);
expect(verifyUrlorCreateDataUrl(url)).toBe(url.href);
expect(verifyUrlOrCreateDataUrl(url)).toBe(url.href);
});

test('verifyUrlorCreateDataUrl: url', async () => {
const url = new URL('../../../node_modules/langium-statemachine-dsl/syntaxes/statemachine.tmLanguage.json', window.location.href);
const text = await (await fetch(url)).text();
expect(verifyUrlorCreateDataUrl(text)).toBe(`data:text/plain;base64,${btoa(text)}`);
expect(verifyUrlOrCreateDataUrl(text)).toBe(`data:text/plain;base64,${btoa(text)}`);
});

test('config userConfiguration', () => {
Expand Down

0 comments on commit 54eb77b

Please sign in to comment.