UNPKG

monaco-editor-wrapper

Version:
299 lines 12.8 kB
/* -------------------------------------------------------------------------------------------- * Copyright (c) 2024 TypeFox and others. * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ import * as vscode from 'vscode'; import * as monaco from '@codingame/monaco-vscode-editor-api'; import { LogLevel } from '@codingame/monaco-vscode-api'; import { createModelReference } from '@codingame/monaco-vscode-api/monaco'; import { ConfigurationTarget, IConfigurationService, StandaloneServices } from '@codingame/monaco-vscode-api'; /** * This is the base class for both Monaco Ediotor Apps: * - EditorAppClassic * - EditorAppExtended * * It provides the generic functionality for both implementations. */ export class EditorApp { $type; id; config; logger; editor; diffEditor; modelRefs; onTextChanged; textChangedDiposeables = {}; modelDisposables = {}; modelRefDisposeTimeout = -1; constructor($type, id, userAppConfig, logger) { this.$type = $type; this.id = id; this.logger = logger; this.config = { codeResources: userAppConfig?.codeResources ?? undefined, useDiffEditor: userAppConfig?.useDiffEditor ?? false, readOnly: userAppConfig?.readOnly ?? false, domReadOnly: userAppConfig?.domReadOnly ?? false, overrideAutomaticLayout: userAppConfig?.overrideAutomaticLayout ?? true }; this.config.editorOptions = { ...userAppConfig?.editorOptions, automaticLayout: userAppConfig?.overrideAutomaticLayout ?? true }; this.config.diffEditorOptions = { ...userAppConfig?.diffEditorOptions, automaticLayout: userAppConfig?.overrideAutomaticLayout ?? true }; this.config.languageDef = userAppConfig?.languageDef; } getConfig() { return this.config; } haveEditor() { return this.editor !== undefined || this.diffEditor !== undefined; } getEditor() { return this.editor; } getDiffEditor() { return this.diffEditor; } getTextModels() { return { modified: this.modelRefs.modified.object.textEditorModel, original: this.modelRefs.original?.object.textEditorModel ?? undefined }; } registerOnTextChangedCallbacks(onTextChanged) { this.onTextChanged = onTextChanged; } setModelRefDisposeTimeout(modelRefDisposeTimeout) { this.modelRefDisposeTimeout = modelRefDisposeTimeout; } async init() { const languageDef = this.config.languageDef; if (languageDef) { if (this.$type === 'extended') { throw new Error('Language definition is not supported for extended editor apps where textmate is used.'); } // register own language first monaco.languages.register(languageDef.languageExtensionConfig); const languageRegistered = monaco.languages.getLanguages().filter(x => x.id === languageDef.languageExtensionConfig.id); if (languageRegistered.length === 0) { // this is only meaningful for languages supported by monaco out of the box monaco.languages.register({ id: languageDef.languageExtensionConfig.id }); } // apply monarch definitions if (languageDef.monarchLanguage) { monaco.languages.setMonarchTokensProvider(languageDef.languageExtensionConfig.id, languageDef.monarchLanguage); } if (languageDef.theme) { monaco.editor.defineTheme(languageDef.theme.name, languageDef.theme.data); monaco.editor.setTheme(languageDef.theme.name); } } if (this.config.editorOptions?.['semanticHighlighting.enabled'] !== undefined) { StandaloneServices.get(IConfigurationService).updateValue('editor.semanticHighlighting.enabled', this.config.editorOptions['semanticHighlighting.enabled'], ConfigurationTarget.USER); } // ensure proper default resources are initialized, uris have to be unique const modified = { text: this.config.codeResources?.modified?.text ?? '', uri: this.config.codeResources?.modified?.uri ?? `default-uri-modified-${this.id}`, enforceLanguageId: this.config.codeResources?.modified?.enforceLanguageId ?? undefined }; this.modelRefs = { modified: await this.buildModelReference(modified, this.logger) }; if (this.config.useDiffEditor === true) { const original = { text: this.config.codeResources?.original?.text ?? '', uri: this.config.codeResources?.original?.uri ?? `default-uri-original-${this.id}`, enforceLanguageId: this.config.codeResources?.original?.enforceLanguageId ?? undefined }; this.modelRefs.original = await this.buildModelReference(original, this.logger); } this.logger?.info('Init of EditorApp was completed.'); } async createEditors(htmlContainer) { if (this.config.useDiffEditor === true) { this.diffEditor = monaco.editor.createDiffEditor(htmlContainer, this.config.diffEditorOptions); const model = { modified: this.modelRefs.modified.object.textEditorModel, original: this.modelRefs.original.object.textEditorModel }; this.diffEditor.setModel(model); this.announceModelUpdate(model); } else { const model = { modified: this.modelRefs.modified.object.textEditorModel }; this.editor = monaco.editor.create(htmlContainer, { ...this.config.editorOptions, model: model.modified }); this.announceModelUpdate(model); } } async updateCodeResources(codeResources) { let updateModified = false; let updateOriginal = false; if (codeResources?.modified !== undefined && codeResources.modified.uri !== this.modelRefs.modified.object.resource.path) { this.modelDisposables.modified = this.modelRefs.modified; this.modelRefs.modified = await this.buildModelReference(codeResources.modified, this.logger); updateModified = true; } if (codeResources?.original !== undefined && codeResources.original.uri !== this.modelRefs.original?.object.resource.path) { this.modelDisposables.original = this.modelRefs.original; this.modelRefs.original = await this.buildModelReference(codeResources.original, this.logger); updateOriginal = true; } if (this.config.useDiffEditor === true) { if (updateModified && updateOriginal) { const model = { modified: this.modelRefs.modified.object.textEditorModel, original: this.modelRefs.original.object.textEditorModel }; this.diffEditor?.setModel(model); this.announceModelUpdate(model); } else { this.logger?.info('Diff Editor: Code resources were not updated. They are ether unchanged or undefined.'); } } else { if (updateModified) { const model = { modified: this.modelRefs.modified.object.textEditorModel }; this.editor?.setModel(model.modified); this.announceModelUpdate(model); } else { this.logger?.info('Editor: Code resources were not updated. They are either unchanged or undefined.'); } } await this.disposeModelRefs(); } async buildModelReference(codeContent, logger) { const code = codeContent.text; const modelRef = await createModelReference(vscode.Uri.parse(codeContent.uri), code); // update the text if different if (modelRef.object.textEditorModel?.getValue() !== code) { modelRef.object.textEditorModel?.setValue(code); } const enforceLanguageId = codeContent.enforceLanguageId; if (enforceLanguageId !== undefined) { modelRef.object.setLanguageId(enforceLanguageId); logger?.info(`Main languageId is enforced: ${enforceLanguageId}`); } return modelRef; } ; announceModelUpdate(textModels) { if (this.onTextChanged !== undefined) { let changed = false; if (textModels.modified !== undefined && textModels.modified !== null) { const old = this.textChangedDiposeables.modified; this.textChangedDiposeables.modified = textModels.modified.onDidChangeContent(() => { didModelContentChange(textModels, this.onTextChanged); }); old?.dispose(); changed = true; } if (textModels.original !== undefined && textModels.original !== null) { const old = this.textChangedDiposeables.original; this.textChangedDiposeables.original = textModels.original.onDidChangeContent(() => { didModelContentChange(textModels, this.onTextChanged); }); old?.dispose(); changed = true; } if (changed) { // do it initially didModelContentChange(textModels, this.onTextChanged); } } } async dispose() { if (this.editor) { this.editor.dispose(); this.editor = undefined; } if (this.diffEditor) { this.diffEditor.dispose(); this.diffEditor = undefined; } this.textChangedDiposeables.modified?.dispose(); this.textChangedDiposeables.original?.dispose(); await this.disposeModelRefs(); } async disposeModelRefs() { const diposeRefs = () => { if (this.logger?.getLevel() === LogLevel.Debug) { const models = monaco.editor.getModels(); this.logger.debug('Current model URIs:'); models.forEach((model, _index) => { this.logger?.debug(`${model.uri.toString()}`); }); } if (this.modelDisposables.modified !== undefined && !this.modelDisposables.modified.object.isDisposed()) { this.modelDisposables.modified.dispose(); this.modelDisposables.modified = undefined; } if (this.modelDisposables.original !== undefined && !this.modelDisposables.original.object.isDisposed()) { this.modelDisposables.original.dispose(); this.modelDisposables.original = undefined; } if (this.logger?.getLevel() === LogLevel.Debug) { if (this.modelDisposables.modified === undefined && this.modelDisposables.original === undefined) { this.logger.debug('All model references are disposed.'); } else { this.logger.debug('Model references are still available.'); } } }; if (this.modelRefDisposeTimeout > 0) { this.logger?.debug('Using async dispose of model references'); await new Promise(resolve => setTimeout(() => { diposeRefs(); resolve(); }, this.modelRefDisposeTimeout)); } else { diposeRefs(); } } updateLayout() { if (this.config.useDiffEditor ?? false) { this.diffEditor?.layout(); } else { this.editor?.layout(); } } } export const verifyUrlOrCreateDataUrl = (input) => { if (input instanceof URL) { return input.href; } else { const bytes = new TextEncoder().encode(input); const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join(''); const base64 = btoa(binString); return new URL(`data:text/plain;base64,${base64}`).href; } }; export const didModelContentChange = (textModels, onTextChanged) => { const modified = textModels.modified?.getValue() ?? ''; const original = textModels.original?.getValue() ?? ''; onTextChanged?.({ modified, original }); }; //# sourceMappingURL=editorApp.js.map