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

Improve configuration change detection #47

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
121 changes: 91 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;
kaisalmen marked this conversation as resolved.
Show resolved Hide resolved
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,60 @@ export abstract class EditorAppBase {
export const isVscodeApiEditorApp = (wrapperConfig: WrapperConfig) => {
return wrapperConfig.editorAppConfig?.$type === 'vscodeApi';
};

export const isCodeUpdateRequired = (config: EditorAppBaseConfig, modelUpdate: ModelUpdate) => {
const updateRequired = (modelUpdate.code !== undefined && modelUpdate.code !== config.code) || modelUpdate.codeOriginal !== config.codeOriginal;
return updateRequired ? ModelUpdateType.code : ModelUpdateType.none;
};

export const isModelUpdateRequired = (config: EditorAppBaseConfig, modelUpdate: ModelUpdate): ModelUpdateType => {
const codeUpdate = isCodeUpdateRequired(config, modelUpdate);

type ModelUpdateKeys = keyof typeof modelUpdate;
const propsModelUpdate = ['languageId', 'codeUri', 'codeOriginalUri'];
const propCompare = (name: string) => {
return config[name as ModelUpdateKeys] !== modelUpdate[name as ModelUpdateKeys];
};
const updateRequired = propsModelUpdate.some(propCompare);
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 => {

let different = includeModelData ? isModelUpdateRequired(orgConfig, config) !== ModelUpdateType.none : false;
if (orgConfig.$type === config.$type) {

type ClassicKeys = keyof typeof orgConfig;
const propsClassic = ['useDiffEditor', 'readOnly', 'domReadOnly', 'userConfiguration', 'automaticLayout', 'languageDef', 'languageExtensionConfig', 'theme', 'themeData'];
const propsClassicEditorOptions = ['editorOptions', 'diffEditorOptions'];

const propCompareClassic = (name: string) => {
return orgConfig[name as ClassicKeys] !== config[name as ClassicKeys];
};

const propsVscode = ['useDiffEditor', 'readOnly', 'domReadOnly', 'userConfiguration', 'extension', 'extensionFilesOrContents'];
type VscodeApiKeys = keyof typeof orgConfig;
const propCompareVscodeApi = (name: string) => {
return orgConfig[name as VscodeApiKeys] !== config[name as VscodeApiKeys];
};

if (orgConfig.$type === 'classic' && config.$type === 'classic') {
different = different || propsClassic.some(propCompareClassic);
if (includeEditorOptions) {
different = different || propsClassicEditorOptions.some(propCompareClassic);
}
} else if (orgConfig.$type === 'vscodeApi' && config.$type === 'vscodeApi') {
different = different || propsVscode.some(propCompareVscodeApi);
}
} 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