Skip to content
This repository has been archived by the owner on Mar 22, 2024. It is now read-only.

Commit

Permalink
#46 Improve configuration change detection (appConfig and model)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaisalmen committed Sep 19, 2023
1 parent 9115ccb commit 08c72db
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 106 deletions.
77 changes: 47 additions & 30 deletions packages/examples/src/reactTs.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 = <MonacoEditorReactComp
userConfig={userConfig}
style={{
'paddingTop': '5px',
'height': '80vh'
}}
onTextChanged={onTextChanged}
/>;
const onTextChanged = (text: string, isDirty: boolean) => {
console.log(`Dirty? ${isDirty} Content: ${text}`);
};

return (
<>
<button onClick={addConsoleMessage}>
Update Code
</button>
<MonacoEditorReactComp
userConfig={userConfig}
style={{
'paddingTop': '5px',
'height': '80vh'
}}
onTextChanged={onTextChanged}
/>
</>

);
};

const comp = <EditorDemo />;
const rootElem = document.getElementById('root')!;
const root = ReactDOM.createRoot(rootElem);
root.render(comp);
4 changes: 2 additions & 2 deletions packages/examples/src/wrapperTs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
}
Expand Down
36 changes: 11 additions & 25 deletions packages/monaco-editor-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -63,37 +63,23 @@ export class MonacoEditorReactComp extends React.Component<MonacoEditorProps> {
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
});
}
}

}
}
}
Expand Down
117 changes: 87 additions & 30 deletions packages/monaco-editor-wrapper/src/editorAppBase.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<void> {
Expand All @@ -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<void> {
Expand Down Expand Up @@ -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') {
Expand All @@ -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'));
}
Expand All @@ -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;
};
12 changes: 10 additions & 2 deletions packages/monaco-editor-wrapper/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {
EditorAppBase,
isVscodeApiEditorApp
isVscodeApiEditorApp,
isCodeUpdateRequired,
isModelUpdateRequired,
isAppConfigDifferent,
ModelUpdateType
} from './editorAppBase.js';

import type {
EditorAppBaseConfig,
EditorAppType,
ModelUpdate,
UserConfiguration,
} from './editorAppBase.js';

Expand Down Expand Up @@ -44,7 +49,6 @@ import {

import type {
UserConfig,
ModelUpdate,
WrapperConfig
} from './wrapper.js';

Expand Down Expand Up @@ -87,6 +91,10 @@ export {
LanguageClientWrapper,
EditorAppBase,
isVscodeApiEditorApp,
isCodeUpdateRequired,
isModelUpdateRequired,
isAppConfigDifferent,
ModelUpdateType,
EditorAppClassic,
EditorAppVscodeApi,
Logger
Expand Down
10 changes: 1 addition & 9 deletions packages/monaco-editor-wrapper/src/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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
Expand Down
Loading

0 comments on commit 08c72db

Please sign in to comment.