monaco-editor-lc-comp
Version:
Lit Component for Monaco Languageclient
265 lines (225 loc) • 8.75 kB
text/typescript
// Monaco Editor Imports
import * as monaco from 'monaco-editor-core';
import editorWorker from 'monaco-editor-core/esm/vs/editor/editor.worker?worker&inline';
import { MonacoLanguageClient, MessageConnection, CloseAction, ErrorAction, MonacoServices, createConnection } from 'monaco-languageclient';
import { listen } from '@codingame/monaco-jsonrpc';
import normalizeUrl from 'normalize-url';
export type WebSocketConfigOptions = {
wsSecured: boolean;
wsHost: string;
wsPort: number;
wsPath: string;
}
export class CodeEditorConfig {
useDiffEditor = false;
codeOriginal: [string, string] = ['', 'javascript'];
codeModified: [string, string] = ['', 'javascript'];
theme = 'vs-light';
monacoEditorOptions: Record<string, unknown> = {
readOnly: false
};
webSocketOptions: WebSocketConfigOptions = {
wsSecured: false,
wsHost: 'localhost',
wsPort: 8080,
wsPath: ''
};
monacoDiffEditorOptions: Record<string, unknown> = {
readOnly: false
};
languageDef: monaco.languages.IMonarchLanguage | undefined = undefined;
themeData: monaco.editor.IStandaloneThemeData | undefined = undefined;
}
export class WorkerOverride {
// static worker load override functions
static getEditorWorker() {
return new editorWorker();
}
}
export class MonacoLanguageClientWrapper {
private editor: monaco.editor.IStandaloneCodeEditor | undefined;
private diffEditor: monaco.editor.IStandaloneDiffEditor | undefined;
private editorConfig: CodeEditorConfig = new CodeEditorConfig();
private id: string;
constructor(id: string) {
this.id = id;
}
getEditorConfig() {
return this.editorConfig;
}
updateTheme() {
monaco.editor.setTheme(this.editorConfig.theme);
}
setUseDiffEditor(useDiffEditor: boolean) {
this.editorConfig.useDiffEditor = useDiffEditor;
}
startEditor(container?: HTMLElement, dispatchEvent?: (event: Event) => boolean) {
console.log(`Starting monaco-editor (${this.id})`);
// register Worker function
this.defineMonacoEnvironment();
if (this.editorConfig.useDiffEditor) {
this.diffEditor = monaco.editor.createDiffEditor(container!);
}
else {
this.editor = monaco.editor.create(container!);
this.editor.getModel()!.onDidChangeContent(() => {
if (dispatchEvent) {
dispatchEvent(new CustomEvent('ChangeContent', { detail: {} }));
}
});
}
this.updateEditor();
this.installMonaco();
this.establishWebSocket(this.editorConfig.webSocketOptions);
}
swapEditors(container?: HTMLElement, dispatchEvent?: (event: Event) => boolean): void {
if (this.editorConfig.useDiffEditor) {
if (this.editor) {
this.editor?.dispose();
this.editor = undefined;
}
if (!this.diffEditor) {
this.startEditor(container, dispatchEvent);
}
}
else {
if (this.diffEditor) {
this.diffEditor?.dispose();
this.diffEditor = undefined;
}
if (!this.editor) {
this.startEditor(container, dispatchEvent);
}
}
}
updateEditor() {
if (this.editorConfig.useDiffEditor) {
this.updateDiffEditor();
}
else {
this.updateMainEditor();
}
}
private updateMainEditor() {
this.updateCommonEditorConfig();
const options = this.editorConfig.monacoEditorOptions as monaco.editor.IEditorOptions & monaco.editor.IGlobalEditorOptions;
this.editor?.updateOptions(options);
const currentModel = this.editor?.getModel();
const languageId = this.editorConfig.codeOriginal[1];
if (languageId && currentModel && currentModel.getLanguageId() !== languageId) {
monaco.editor.setModelLanguage(currentModel, languageId);
}
if (this.editorConfig.codeOriginal[0]) {
this.editor?.setValue(this.editorConfig.codeOriginal[0]);
}
this.updateLayout();
}
private updateDiffEditor() {
this.updateCommonEditorConfig();
const options = this.editorConfig.monacoDiffEditorOptions as monaco.editor.IDiffEditorOptions & monaco.editor.IGlobalEditorOptions;
this.diffEditor?.updateOptions(options);
this.updateDiffModels();
this.updateLayout();
}
private updateCommonEditorConfig() {
const languageId = this.editorConfig.codeOriginal[1];
// apply monarch definitions
if (this.editorConfig.languageDef && languageId) {
monaco.languages.register({ id: languageId });
monaco.languages.setMonarchTokensProvider(languageId, this.editorConfig.languageDef);
}
if (this.editorConfig.themeData) {
monaco.editor.defineTheme(this.editorConfig.theme as string, this.editorConfig.themeData);
}
this.updateTheme();
}
private updateDiffModels() {
if (this.diffEditor) {
const originalModel = monaco.editor.createModel(this.editorConfig.codeOriginal[0], this.editorConfig.codeOriginal[1]);
const modifiedModel = monaco.editor.createModel(this.editorConfig.codeModified[0], this.editorConfig.codeModified[1]);
this.diffEditor.setModel({
original: originalModel,
modified: modifiedModel
});
}
}
updateLayout() {
if (this.editorConfig.useDiffEditor) {
this.diffEditor?.layout();
}
else {
this.editor?.layout();
}
}
private defineMonacoEnvironment() {
const getWorker = (_: string, label: string) => {
console.log('getWorker: workerId: ' + _ + ' label: ' + label);
return WorkerOverride.getEditorWorker();
};
const monWin = (self as monaco.Window);
if (monWin) {
if (!monWin.MonacoEnvironment) {
monWin.MonacoEnvironment = {
getWorker: getWorker
};
}
else {
monWin.MonacoEnvironment.getWorker = getWorker;
}
}
}
private installMonaco() {
// install Monaco language client services
if (monaco) {
try {
MonacoServices.get();
}
catch (e: unknown) {
// install only if services are not yet available (exception will happen only then)
MonacoServices.install(monaco);
console.log(`Component (${this.id}): Installed MonacoServices`);
}
}
}
private establishWebSocket(websocketConfig: WebSocketConfigOptions) {
// create the web socket
const url = this.createUrl(websocketConfig);
const webSocket = new WebSocket(url);
// listen when the web socket is opened
listen({
webSocket,
onConnection: connection => {
console.log('Connected');
// create and start the language client
const languageClient = this.createLanguageClient(connection);
const disposable = languageClient.start();
connection.onClose(() => disposable.dispose());
}
});
}
private createLanguageClient(connection: MessageConnection): MonacoLanguageClient {
return new MonacoLanguageClient({
name: 'Sample Language Client',
clientOptions: {
// use a language id as a document selector
documentSelector: [this.editorConfig.codeOriginal[1]],
// disable the default error handler
errorHandler: {
error: () => ErrorAction.Continue,
closed: () => CloseAction.DoNotRestart
}
},
// create a language client connection from the JSON RPC connection on demand
connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
}
}
});
}
private createUrl(websocketConfig: WebSocketConfigOptions) {
const protocol = websocketConfig.wsSecured ? 'wss' : 'ws';
return normalizeUrl(`${protocol}://${websocketConfig.wsHost}:${websocketConfig.wsPort}/${websocketConfig.wsPath}`);
}
}
export { monaco };