Files
theia-code-os/packages/plugin-ext/src/main/browser/custom-editors/plugin-custom-editor-registry.ts
mawkone 8bb5110148
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled
deploy: current vibn theia state
Made-with: Cursor
2026-02-27 12:01:08 -08:00

127 lines
5.4 KiB
TypeScript

// *****************************************************************************
// Copyright (C) 2021 SAP SE or an SAP affiliate company and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { CustomEditor, DeployedPlugin } from '../../../common';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { CustomEditorOpener } from './custom-editor-opener';
import { Emitter, PreferenceService } from '@theia/core';
import { ApplicationShell, DefaultOpenerService, OpenWithService, WidgetManager } from '@theia/core/lib/browser';
import { CustomEditorWidget } from './custom-editor-widget';
@injectable()
export class PluginCustomEditorRegistry {
private readonly editors = new Map<string, CustomEditor>();
private readonly pendingEditors = new Map<CustomEditorWidget, { deferred: Deferred<void>, disposable: Disposable }>();
private readonly resolvers = new Map<string, (widget: CustomEditorWidget) => Promise<void>>();
private readonly onWillOpenCustomEditorEmitter = new Emitter<string>();
readonly onWillOpenCustomEditor = this.onWillOpenCustomEditorEmitter.event;
@inject(DefaultOpenerService)
protected readonly defaultOpenerService: DefaultOpenerService;
@inject(WidgetManager)
protected readonly widgetManager: WidgetManager;
@inject(ApplicationShell)
protected readonly shell: ApplicationShell;
@inject(OpenWithService)
protected readonly openWithService: OpenWithService;
@inject(PreferenceService)
protected readonly preferenceService: PreferenceService;
@postConstruct()
protected init(): void {
this.widgetManager.onDidCreateWidget(({ factoryId, widget }) => {
if (factoryId === CustomEditorWidget.FACTORY_ID && widget instanceof CustomEditorWidget) {
const restoreState = widget.restoreState.bind(widget);
widget.restoreState = state => {
if (state.viewType && state.strResource) {
restoreState(state);
this.resolveWidget(widget);
} else {
widget.dispose();
}
};
}
});
}
registerCustomEditor(editor: CustomEditor, plugin: DeployedPlugin): Disposable {
if (this.editors.has(editor.viewType)) {
console.warn('editor with such id already registered: ', JSON.stringify(editor));
return Disposable.NULL;
}
this.editors.set(editor.viewType, editor);
const toDispose = new DisposableCollection();
toDispose.push(Disposable.create(() => this.editors.delete(editor.viewType)));
const editorOpenHandler = new CustomEditorOpener(
editor,
this.shell,
this.widgetManager,
this,
this.preferenceService
);
toDispose.push(this.defaultOpenerService.addHandler(editorOpenHandler));
toDispose.push(
this.openWithService.registerHandler({
id: editor.viewType,
label: editorOpenHandler.label,
providerName: plugin.metadata.model.displayName,
canHandle: uri => editorOpenHandler.canOpenWith(uri),
open: uri => editorOpenHandler.open(uri)
})
);
return toDispose;
}
async resolveWidget(widget: CustomEditorWidget): Promise<void> {
const resolver = this.resolvers.get(widget.viewType);
if (resolver) {
await resolver(widget);
} else {
const deferred = new Deferred<void>();
const disposable = widget.onDidDispose(() => this.pendingEditors.delete(widget));
this.pendingEditors.set(widget, { deferred, disposable });
this.onWillOpenCustomEditorEmitter.fire(widget.viewType);
return deferred.promise;
}
};
registerResolver(viewType: string, resolver: (widget: CustomEditorWidget) => Promise<void>): Disposable {
if (this.resolvers.has(viewType)) {
throw new Error(`Resolver for ${viewType} already registered`);
}
for (const [editorWidget, { deferred, disposable }] of this.pendingEditors.entries()) {
if (editorWidget.viewType === viewType) {
resolver(editorWidget).then(() => deferred.resolve(), err => deferred.reject(err)).finally(() => disposable.dispose());
this.pendingEditors.delete(editorWidget);
}
}
this.resolvers.set(viewType, resolver);
return Disposable.create(() => this.resolvers.delete(viewType));
}
}