Skip to content

Commit

Permalink
fix(editor-monaco): track lifecycle of monaco/vscode initialization (#…
Browse files Browse the repository at this point in the history
…4344)

This will allow language plugins to inspect
the state of monaco/vscode initialization and
hook wherever and whenever needed.

Initialization of services only happens once.
Initialization of extensions happens after services initialization and happens only once as well.
If SwaggerEditor@5 is unmounted and mounted again the initialization is not run again.
Initialization phases are now tracked in window.MonacoEnvironment.initPhase.

SwaggerUI plugin system roon inject named monacoInitializationDeferred is 
now exposed to plugin system.
  • Loading branch information
char0n committed Aug 4, 2023
1 parent 5de09b3 commit bf9d455
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 53 deletions.
45 changes: 25 additions & 20 deletions src/plugins/editor-monaco-language-apidom/language/apidom-mode.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { languages as vscodeLanguages } from 'vscode';
import { onExtHostInitialized } from 'vscode/extensions';
import { createConverter as createCodeConverter } from 'vscode-languageclient/lib/common/codeConverter.js';
import { createConverter as createProtocolConverter } from 'vscode-languageclient/lib/common/protocolConverter.js';

Expand Down Expand Up @@ -35,16 +34,18 @@ export const getWorker = () => {
const registerProviders = ({ languageId, providers, dependencies }) => {
disposeAll(providers);

const { worker, codeConverter, protocolConverter } = dependencies;
const { worker, codeConverter, protocolConverter, getSystem } = dependencies;
const args = [worker, codeConverter, protocolConverter];
const system = getSystem();

/**
* Customized providers needs to be registered before monaco editor is created
* and services are initialized.
* Customized providers needs to be registered before monaco editor is created.
*/
providers.push(new DiagnosticsProvider(...args));
providers.push(new DiagnosticsProvider(...args, getSystem));

(async () => {
await system.monacoInitializationDeferred().promise;

onExtHostInitialized(() => {
providers.push(vscodeLanguages.registerHoverProvider(languageId, new HoverProvider(...args)));
providers.push(
vscodeLanguages.registerDocumentLinkProvider(languageId, new DocumentLinkProvider(...args))
Expand All @@ -68,19 +69,18 @@ const registerProviders = ({ languageId, providers, dependencies }) => {
vscodeLanguages.registerDefinitionProvider(languageId, new DefinitionProvider(...args))
);

(async () => {
const workerService = await worker();
const semanticTokensLegend = await workerService.getSemanticTokensLegend();

providers.push(
vscodeLanguages.registerDocumentSemanticTokensProvider(
languageId,
new DocumentSemanticTokensProvider(...args),
semanticTokensLegend
)
);
})();
});
const workerService = await worker();
const semanticTokensLegend = await workerService.getSemanticTokensLegend();

providers.push(
vscodeLanguages.registerDocumentSemanticTokensProvider(
languageId,
new DocumentSemanticTokensProvider(...args),
semanticTokensLegend
)
);
})();
system.monacoInitializationDeferred().promise.then(() => {});

return providers;
};
Expand Down Expand Up @@ -112,7 +112,12 @@ export function setupMode(defaults) {
registerProviders({
languageId: defaults.getLanguageId(),
providers,
dependencies: { worker, codeConverter, protocolConverter },
dependencies: {
worker,
codeConverter,
protocolConverter,
getSystem: defaults.getModeConfiguration().getSystem,
},
})
)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as monaco from 'monaco-editor';
import { languages as vscodeLanguages } from 'vscode';
import { ModesRegistry } from 'monaco-editor/esm/vs/editor/common/languages/modesRegistry.js';
import { onExtHostInitialized } from 'vscode/extensions';

import * as apidom from './apidom.js';
import { setupMode } from './apidom-mode.js';
Expand Down Expand Up @@ -81,7 +80,7 @@ const lazyMonacoContribution = ({ createData, system }) => {
})
);

onExtHostInitialized(() => {
system.monacoInitializationDeferred().promise.then(() => {
disposables.push(vscodeLanguages.setLanguageConfiguration(apidom.languageId, apidom.conf));
});

Expand All @@ -91,7 +90,10 @@ const lazyMonacoContribution = ({ createData, system }) => {
disposables.push(
monaco.editor.onDidCreateEditor(() => {
const { customApiDOMWorkerPath: customWorkerPath, apiDOMContext, ...data } = createData;
const defaults = new LanguageServiceDefaultsImpl({ apiDOMContext, customWorkerPath, data });
const defaults = new LanguageServiceDefaultsImpl(
{ apiDOMContext, customWorkerPath, data },
{ getSystem: system.getSystem }
);

disposables.push(setupMode(defaults));
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as monaco from 'monaco-editor';
import { languages as vscodeLanguages } from 'vscode';
import { onExtHostInitialized } from 'vscode/extensions';

import Provider from './Provider.js';
import * as apidom from '../apidom.js';
Expand All @@ -12,8 +11,11 @@ class DiagnosticsProvider extends Provider {

#diagnosticCollection = null;

constructor(...args) {
super(...args);
#system = null;

constructor(worker, codeConverter, protocolConverter, getSystem) {
super(worker, codeConverter, protocolConverter);
this.#system = getSystem();

const onModelAdded = (model) => {
if (model.getLanguageId() !== apidom.languageId) {
Expand Down Expand Up @@ -61,15 +63,17 @@ class DiagnosticsProvider extends Provider {
}
};

const onVscodeExtensionsInitialized = () => {
const onVscodeInitialized = () => {
this.#diagnosticCollection = vscodeLanguages.createDiagnosticCollection(apidom.languageId);
this.#disposables.push(this.#diagnosticCollection);
return this.#diagnosticCollection;
};

this.#disposables.push(monaco.editor.onDidCreateModel(onModelAdded));
this.#disposables.push(monaco.editor.onDidChangeModelLanguage(onModelLanguageChanged));
this.#disposables.push(monaco.editor.onWillDisposeModel(onModelRemoved));
onExtHostInitialized(onVscodeExtensionsInitialized);
this.#system.monacoInitializationDeferred().promise.then(() => {
this.#disposables.push(onVscodeInitialized());
});
}

async #getDiagnostics(model) {
Expand All @@ -90,7 +94,7 @@ class DiagnosticsProvider extends Provider {
async #validate(model) {
const diagnostics = await this.#getDiagnostics(model);

this.#diagnosticCollection.set(
this.#diagnosticCollection?.set(
model.uri,
await this.protocolConverter.asDiagnostics(diagnostics)
);
Expand All @@ -100,6 +104,7 @@ class DiagnosticsProvider extends Provider {
super.dispose();
this.#disposables.forEach((disposable) => disposable?.dispose());
this.#disposables = [];
this.#system = null;
}
}

Expand Down
58 changes: 36 additions & 22 deletions src/plugins/editor-monaco/after-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,51 @@ import {
import getLanguagesServiceOverride from 'vscode/service-override/languages';
import { initialize as initializeVscodeExtensions } from 'vscode/extensions';

function afterLoad() {
function afterLoad(system) {
const InitPhase = {
UNINITIALIZED: 'UNINITIALIZED',
IN_PROGRESS: 'IN_PROGRESS',
INITIALIZED: 'INITIALIZED',
};

// setup monaco environment
globalThis.MonacoEnvironment = {
initPhase: InitPhase.UNINITIALIZED,
baseUrl: document.baseURI || location.href, // eslint-disable-line no-restricted-globals
getWorkerUrl() {
return new URL(process.env.REACT_APP_APIDOM_WORKER_FILENAME, this.baseUrl).toString();
},
...globalThis.MonacoEnvironment, // this will allow to override the base uri for loading Web Workers
};

/**
* Monaco standalone services is a singleton and can be initialized only once.
* Subsequent initializations are noops. This has a side effect which
* is inability to dispose of the services when no longer needed.
* Monaco standalone services can be initialized only once.
* Standalone services cannot be disposed of when no longer needed.
* Individual services can be disposed of separately, but if one decides
* to do that, `initialize` function will not able to initialize them again.
*
* Extensions needs to initialized explicitly as well.
*/
if (!this.vscodeInitStarted) {
if (globalThis.MonacoEnvironment.initPhase === InitPhase.UNINITIALIZED) {
globalThis.MonacoEnvironment.initPhase = InitPhase.IN_PROGRESS;

(async () => {
this.vscodeInitStarted = true;
await Promise.all([
initializeMonacoServices({
...getLanguagesServiceOverride(),
}),
initializeVscodeExtensions(),
]);
StandaloneServices.get(IStorageService).store('expandSuggestionDocs', true, 0, 0);
try {
await Promise.all([
initializeMonacoServices({
...getLanguagesServiceOverride(),
}),
initializeVscodeExtensions(),
]);
StandaloneServices.get(IStorageService).store('expandSuggestionDocs', true, 0, 0);
system.monacoInitializationDeferred().resolve();
} catch (error) {
system.monacoInitializationDeferred().reject(error);
} finally {
globalThis.MonacoEnvironment.initPhase = InitPhase.INITIALIZED;
}
})();
}

// setup monaco environment
globalThis.MonacoEnvironment = {
baseUrl: document.baseURI || location.href, // eslint-disable-line no-restricted-globals
getWorkerUrl() {
return new URL(process.env.REACT_APP_APIDOM_WORKER_FILENAME, this.baseUrl).toString();
},
...(globalThis.MonacoEnvironment || {}), // this will allow to override the base uri for loading Web Workers
};
}

export default afterLoad;
9 changes: 9 additions & 0 deletions src/plugins/editor-monaco/fn.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ export const waitUntil = async (condition, { interval = 100, maxWait = 2500 } =
}, maxWait);
});
};

export const makeDeferred = () => {
const deferred = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
};
3 changes: 2 additions & 1 deletion src/plugins/editor-monaco/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import { setTheme } from './actions/set-theme.js';
import reducers from './reducers.js';
import { selectTheme, selectMarkers, selectLanguage, selectEditor } from './selectors.js';
import { registerMarkerDataProvider, waitUntil } from './fn.js';
import { monaco } from './root-injects.js';
import { monaco, monacoInitializationDeferred } from './root-injects.js';
import afterLoad from './after-load.js';

const EditorMonacoPlugin = () => ({
afterLoad,
rootInjects: {
monaco,
monacoInitializationDeferred: () => monacoInitializationDeferred,
},
components: {
Editor: MonacoEditorContainer,
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/editor-monaco/root-injects.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
import { makeDeferred } from './fn.js';

export * as monaco from 'monaco-editor';

export const monacoInitializationDeferred = makeDeferred();

0 comments on commit bf9d455

Please sign in to comment.