diff --git a/packages/examples/src/reactTs.tsx b/packages/examples/src/reactTs.tsx index eaba6fc..6d3e4c3 100644 --- a/packages/examples/src/reactTs.tsx +++ b/packages/examples/src/reactTs.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import ReactDOM from 'react-dom/client'; +import React, { useState } from 'react'; import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; import { UserConfig } from 'monaco-editor-wrapper'; @@ -9,38 +9,55 @@ import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; import { buildWorkerDefinition } from 'monaco-editor-workers'; buildWorkerDefinition('../../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); -const rootElem = document.getElementById('root')!; -const userConfig: UserConfig = { - htmlElement: rootElem, - wrapperConfig: { - serviceConfig: { - enableKeybindingsService: true, - debugLogging: true - }, - editorAppConfig: { - $type: 'classic', - languageId: 'typescript', - useDiffEditor: false, - theme: 'vs-dark', - code: `function sayHello(): string { - return "Hello"; -};` +const EditorDemo: React.FC = () => { + const logMessage = 'console.log(\'hello\')'; + const [content, setContent] = useState(logMessage); + + const rootElem = document.getElementById('root')!; + const userConfig: UserConfig = { + htmlElement: rootElem, + wrapperConfig: { + serviceConfig: { + enableKeybindingsService: true, + debugLogging: true + }, + editorAppConfig: { + $type: 'classic', + languageId: 'typescript', + useDiffEditor: false, + theme: 'vs-dark', + code: content + } } - } -}; + }; -const onTextChanged = (text: string, isDirty: boolean) => { - console.log(`Dirty? ${isDirty} Content: ${text}`); -}; + const addConsoleMessage = () => { + setContent(`${content}\n${logMessage}`); + }; -const comp = ; + const onTextChanged = (text: string, isDirty: boolean) => { + console.log(`Dirty? ${isDirty} Content: ${text}`); + }; + return ( + <> + + + + + ); +}; + +const comp = ; +const rootElem = document.getElementById('root')!; const root = ReactDOM.createRoot(rootElem); root.render(comp); diff --git a/packages/examples/src/wrapperTs.ts b/packages/examples/src/wrapperTs.ts index 97304cd..f2c05e5 100644 --- a/packages/examples/src/wrapperTs.ts +++ b/packages/examples/src/wrapperTs.ts @@ -60,13 +60,13 @@ try { if (wrapper.getMonacoEditorApp()?.getConfig().codeUri === codeUri) { updateModel({ code: codeOriginal, - uri: codeOriginalUri, + codeUri: codeOriginalUri, languageId: 'typescript', }); } else { updateModel({ code: code, - uri: codeUri, + codeUri: codeUri, languageId: 'typescript', }); } diff --git a/packages/monaco-editor-react/src/index.tsx b/packages/monaco-editor-react/src/index.tsx index 23dd17d..9eaa628 100644 --- a/packages/monaco-editor-react/src/index.tsx +++ b/packages/monaco-editor-react/src/index.tsx @@ -1,4 +1,4 @@ -import { EditorAppClassic, EditorAppConfigClassic, MonacoEditorLanguageClientWrapper, UserConfig, WorkerConfigDirect, WorkerConfigOptions } from 'monaco-editor-wrapper'; +import { EditorAppClassic, MonacoEditorLanguageClientWrapper, UserConfig, WorkerConfigDirect, WorkerConfigOptions, isAppConfigDifferent } from 'monaco-editor-wrapper'; import { IDisposable } from 'monaco-editor'; import * as vscode from 'vscode'; import React, { CSSProperties } from 'react'; @@ -63,37 +63,23 @@ export class MonacoEditorReactComp extends React.Component { await this.handleReinit(); } else { if (wrapper !== null) { - let restarted = false; + const prevConfig = prevProps.userConfig.wrapperConfig.editorAppConfig; + const config = userConfig.wrapperConfig.editorAppConfig; + const appConfigDifferent = isAppConfigDifferent(prevConfig, config, false, false); // we need to restart if the editor wrapper config changed - if (prevProps.userConfig.wrapperConfig.editorAppConfig !== - userConfig.wrapperConfig.editorAppConfig) { - restarted = true; + if (appConfigDifferent) { await this.handleReinit(); - } + } else { + // the function now ensure a model update is only required if something else than the code changed + this.wrapper.updateModel(userConfig.wrapperConfig.editorAppConfig); - if (!restarted) { - const appConfig = userConfig.wrapperConfig.editorAppConfig; - if (appConfig.$type === 'classic') { - const options = (appConfig as EditorAppConfigClassic).editorOptions; - const prevOptions = (prevProps.userConfig.wrapperConfig.editorAppConfig as EditorAppConfigClassic).editorOptions; - if (options !== prevOptions && options !== undefined) { - (wrapper.getMonacoEditorApp() as EditorAppClassic).updateMonacoEditorOptions(options); + if (prevConfig.$type === 'classic' && config.$type === 'classic') { + if (prevConfig.editorOptions !== config.editorOptions) { + (wrapper.getMonacoEditorApp() as EditorAppClassic).updateMonacoEditorOptions(config.editorOptions ?? {}); } } - - const languageId = appConfig.languageId; - const code = appConfig.code; - const prevLanguageId = prevProps.userConfig.wrapperConfig.editorAppConfig.languageId; - const prevCode = prevProps.userConfig.wrapperConfig.editorAppConfig.code; - if (languageId !== prevLanguageId && code !== prevCode) { - this.wrapper.updateModel({ - languageId: languageId, - code: code - }); - } } - } } } diff --git a/packages/monaco-editor-wrapper/src/editorAppBase.ts b/packages/monaco-editor-wrapper/src/editorAppBase.ts index 611b730..9d92381 100644 --- a/packages/monaco-editor-wrapper/src/editorAppBase.ts +++ b/packages/monaco-editor-wrapper/src/editorAppBase.ts @@ -1,18 +1,21 @@ import { editor, Uri } from 'monaco-editor'; import { createConfiguredEditor, createConfiguredDiffEditor, createModelReference, ITextFileEditorModel } from 'vscode/monaco'; import { IReference } from 'vscode/service-override/editor'; -import { updateUserConfiguration as vscodeUpdateUserConfiguratio } from 'vscode/service-override/configuration'; -import { ModelUpdate, UserConfig, WrapperConfig } from './wrapper.js'; +import { UserConfig, WrapperConfig } from './wrapper.js'; +import { updateUserConfiguration as vscodeUpdateUserConfiguration } from 'vscode/service-override/configuration'; import { EditorAppConfigClassic } from './editorAppClassic.js'; import { EditorAppConfigVscodeApi } from './editorAppVscodeApi.js'; -export type EditorAppBaseConfig = { +export type ModelUpdate = { languageId: string; - code: string; + code?: string; codeUri?: string; - useDiffEditor: boolean; codeOriginal?: string; codeOriginalUri?: string; +} + +export type EditorAppBaseConfig = ModelUpdate & { + useDiffEditor: boolean; domReadOnly?: boolean; readOnly?: boolean; userConfiguration?: UserConfiguration; @@ -114,8 +117,21 @@ export abstract class EditorAppBase { return Promise.reject(new Error('You cannot update the editor model, because the regular editor is not configured.')); } - this.updateAppConfig(modelUpdate); - await this.updateEditorModel(); + const modelUpdateType = isModelUpdateRequired(this.getConfig(), modelUpdate); + + if (modelUpdateType === ModelUpdateType.code) { + this.updateAppConfig(modelUpdate); + if (this.getConfig().useDiffEditor) { + this.diffEditor?.getModifiedEditor().setValue(modelUpdate.code ?? ''); + this.diffEditor?.getOriginalEditor().setValue(modelUpdate.codeOriginal ?? ''); + } else { + this.editor.setValue(modelUpdate.code ?? ''); + } + } else if (modelUpdateType === ModelUpdateType.model) { + this.updateAppConfig(modelUpdate); + await this.updateEditorModel(); + } + return Promise.resolve(); } private async updateEditorModel(): Promise { @@ -134,9 +150,11 @@ export abstract class EditorAppBase { if (!this.diffEditor) { return Promise.reject(new Error('You cannot update the diff editor models, because the diffEditor is not configured.')); } - - this.updateAppConfig(modelUpdate); - return this.updateDiffEditorModel(); + if (isModelUpdateRequired(this.getConfig(), modelUpdate)) { + this.updateAppConfig(modelUpdate); + await this.updateDiffEditorModel(); + } + return Promise.resolve(); } private async updateDiffEditorModel(): Promise { @@ -167,25 +185,11 @@ export abstract class EditorAppBase { private updateAppConfig(modelUpdate: ModelUpdate) { const config = this.getConfig(); - if (modelUpdate.code !== undefined) { - config.code = modelUpdate.code; - } - - if (modelUpdate.languageId !== undefined) { - config.languageId = modelUpdate.languageId; - } - - if (modelUpdate.uri !== undefined) { - config.codeUri = modelUpdate.uri; - } - - if (modelUpdate.codeOriginal !== undefined) { - config.codeOriginal = modelUpdate.codeOriginal; - } - - if (modelUpdate.codeOriginalUri !== undefined) { - config.codeOriginalUri = modelUpdate.codeOriginalUri; - } + config.languageId = modelUpdate.languageId; + config.code = modelUpdate.code; + config.codeUri = modelUpdate.codeUri; + config.codeOriginal = modelUpdate.codeOriginal; + config.codeOriginalUri = modelUpdate.codeOriginalUri; } getEditorUri(uriType: 'code' | 'codeOriginal') { @@ -208,7 +212,7 @@ export abstract class EditorAppBase { async updateUserConfiguration(config: UserConfiguration) { if (config.json) { - return vscodeUpdateUserConfiguratio(config.json); + return vscodeUpdateUserConfiguration(config.json); } return Promise.reject(new Error('Supplied config is undefined')); } @@ -223,3 +227,56 @@ export abstract class EditorAppBase { export const isVscodeApiEditorApp = (wrapperConfig: WrapperConfig) => { return wrapperConfig.editorAppConfig?.$type === 'vscodeApi'; }; + +export const isCodeUpdateRequired = (config: EditorAppBaseConfig, modelUpdate: ModelUpdate) => { + let updateRequired = modelUpdate.code !== undefined && modelUpdate.code !== config.code; + updateRequired = updateRequired || modelUpdate.codeOriginal !== config.codeOriginal; + return updateRequired ? ModelUpdateType.code : ModelUpdateType.none; +}; + +export const isModelUpdateRequired = (config: EditorAppBaseConfig, modelUpdate: ModelUpdate): ModelUpdateType => { + const codeUpdate = isCodeUpdateRequired(config, modelUpdate); + let updateRequired = modelUpdate.languageId !== config.languageId; + updateRequired = updateRequired || modelUpdate.codeUri !== config.codeUri; + updateRequired = updateRequired || modelUpdate.codeOriginalUri !== config.codeOriginalUri; + return updateRequired ? ModelUpdateType.model : codeUpdate; +}; + +export enum ModelUpdateType { + none, + code, + model +} + +export const isAppConfigDifferent = (orgConfig: EditorAppConfigClassic | EditorAppConfigVscodeApi, + config: EditorAppConfigClassic | EditorAppConfigVscodeApi, includeModelData: boolean, includeEditorOptions: boolean): boolean => { + + // this is done by hand, ModelUpdate is only considered if flag is set + let different = includeModelData ? isModelUpdateRequired(orgConfig, config) !== ModelUpdateType.none : false; + if (orgConfig.$type === config.$type) { + if (orgConfig.$type === 'classic' && config.$type === 'classic') { + different = different || orgConfig.automaticLayout !== config.automaticLayout; + different = different || orgConfig.domReadOnly !== config.domReadOnly; + if (includeEditorOptions === true) { + different = different || orgConfig.diffEditorOptions !== config.diffEditorOptions; + different = different || orgConfig.editorOptions !== config.editorOptions; + } + different = different || orgConfig.languageDef !== config.languageDef; + different = different || orgConfig.languageExtensionConfig !== config.languageExtensionConfig; + different = different || orgConfig.readOnly !== config.readOnly; + different = different || orgConfig.theme !== config.theme; + different = different || orgConfig.themeData !== config.themeData; + different = different || orgConfig.useDiffEditor !== config.useDiffEditor; + } else if (orgConfig.$type === 'vscodeApi' && config.$type === 'vscodeApi') { + different = different || orgConfig.domReadOnly !== config.domReadOnly; + different = different || orgConfig.extension !== config.extension; + different = different || orgConfig.extensionFilesOrContents !== config.extensionFilesOrContents; + different = different || orgConfig.readOnly !== config.readOnly; + different = different || orgConfig.useDiffEditor !== config.useDiffEditor; + different = different || orgConfig.userConfiguration !== config.userConfiguration; + } + } else { + throw new Error('Provided configurations are not of the same type.'); + } + return different; +}; diff --git a/packages/monaco-editor-wrapper/src/index.ts b/packages/monaco-editor-wrapper/src/index.ts index b6d7d95..a0a6377 100644 --- a/packages/monaco-editor-wrapper/src/index.ts +++ b/packages/monaco-editor-wrapper/src/index.ts @@ -1,11 +1,16 @@ import { EditorAppBase, - isVscodeApiEditorApp + isVscodeApiEditorApp, + isCodeUpdateRequired, + isModelUpdateRequired, + isAppConfigDifferent, + ModelUpdateType } from './editorAppBase.js'; import type { EditorAppBaseConfig, EditorAppType, + ModelUpdate, UserConfiguration, } from './editorAppBase.js'; @@ -44,7 +49,6 @@ import { import type { UserConfig, - ModelUpdate, WrapperConfig } from './wrapper.js'; @@ -87,6 +91,10 @@ export { LanguageClientWrapper, EditorAppBase, isVscodeApiEditorApp, + isCodeUpdateRequired, + isModelUpdateRequired, + isAppConfigDifferent, + ModelUpdateType, EditorAppClassic, EditorAppVscodeApi, Logger diff --git a/packages/monaco-editor-wrapper/src/wrapper.ts b/packages/monaco-editor-wrapper/src/wrapper.ts index 6a85004..1e242fc 100644 --- a/packages/monaco-editor-wrapper/src/wrapper.ts +++ b/packages/monaco-editor-wrapper/src/wrapper.ts @@ -2,7 +2,7 @@ import { editor } from 'monaco-editor'; import { initServices, wasVscodeApiInitialized, InitializeServiceConfig, MonacoLanguageClient } from 'monaco-languageclient'; import { EditorAppVscodeApi, EditorAppConfigVscodeApi } from './editorAppVscodeApi.js'; import { EditorAppClassic, EditorAppConfigClassic } from './editorAppClassic.js'; -import { UserConfiguration, isVscodeApiEditorApp } from './editorAppBase.js'; +import { ModelUpdate, UserConfiguration, isVscodeApiEditorApp } from './editorAppBase.js'; import { LanguageClientConfig, LanguageClientWrapper } from './languageClientWrapper.js'; import { Logger, LoggerConfig } from './logger.js'; @@ -19,14 +19,6 @@ export type UserConfig = { languageClientConfig?: LanguageClientConfig; } -export type ModelUpdate = { - languageId?: string; - code?: string; - uri?: string; - codeOriginal?: string; - codeOriginalUri?: string; -} - /** * This class is responsible for the overall ochestration. * It inits, start and disposes the editor apps and the language client (if configured) and provides diff --git a/packages/monaco-editor-wrapper/test/editorAppBase.test.ts b/packages/monaco-editor-wrapper/test/editorAppBase.test.ts index 50d3bfa..eb10065 100644 --- a/packages/monaco-editor-wrapper/test/editorAppBase.test.ts +++ b/packages/monaco-editor-wrapper/test/editorAppBase.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import { EditorAppClassic, isVscodeApiEditorApp } from 'monaco-editor-wrapper'; -import { createBaseConfig, createWrapperConfig } from './helper.js'; +import { isAppConfigDifferent, isVscodeApiEditorApp, isModelUpdateRequired, EditorAppClassic, ModelUpdateType } from 'monaco-editor-wrapper'; +import { createBaseConfig, createEditorAppConfig, createWrapperConfig } from './helper.js'; describe('Test EditorAppBase', () => { @@ -36,4 +36,30 @@ describe('Test EditorAppBase', () => { const app = new EditorAppClassic('config defaults', config); expect(app.getConfig().userConfiguration?.json).toEqual('{ "editor.semanticHighlighting.enabled": true }'); }); + + test('isModelUpdateRequired', () => { + const config = createEditorAppConfig('classic'); + let modelUpdateType = isModelUpdateRequired(config, { languageId: 'typescript', code: '' }); + expect(modelUpdateType).toBe(ModelUpdateType.none); + + modelUpdateType = isModelUpdateRequired(config, { languageId: 'typescript' }); + expect(modelUpdateType).toBe(ModelUpdateType.none); + + modelUpdateType = isModelUpdateRequired(config, { languageId: 'typescript', code: 'test' }); + expect(modelUpdateType).toBe(ModelUpdateType.code); + + modelUpdateType = isModelUpdateRequired(config, { languageId: 'javascript', code: 'test' }); + expect(modelUpdateType).toBe(ModelUpdateType.model); + }); + + test('isModelUpdateRequired', () => { + const orgConfig = createEditorAppConfig('classic'); + const config = createEditorAppConfig('classic'); + expect(isAppConfigDifferent(orgConfig, config, false, false)).toBeFalsy(); + + config.code = 'test'; + expect(isAppConfigDifferent(orgConfig, config, false, false)).toBeFalsy(); + expect(isAppConfigDifferent(orgConfig, config, true, false)).toBeTruthy(); + }); + }); diff --git a/packages/monaco-editor-wrapper/test/helper.ts b/packages/monaco-editor-wrapper/test/helper.ts index 7b552be..6fb42f7 100644 --- a/packages/monaco-editor-wrapper/test/helper.ts +++ b/packages/monaco-editor-wrapper/test/helper.ts @@ -15,11 +15,15 @@ export const createBaseConfig = (type: EditorAppType): UserConfig => { export const createWrapperConfig = (type: EditorAppType) => { return { - editorAppConfig: { - $type: type, - languageId: 'typescript', - code: '', - useDiffEditor: false, - } + editorAppConfig: createEditorAppConfig(type) + }; +}; + +export const createEditorAppConfig = (type: EditorAppType) => { + return { + $type: type, + languageId: 'typescript', + code: '', + useDiffEditor: false, }; };