vscode/src/vs/workbench/contrib/notebook/browser/notebookEditorServiceImpl.ts
2021-11-24 13:58:32 +01:00

206 lines
7.5 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ResourceMap } from 'vs/base/common/map';
import { getDefaultNotebookCreationOptions, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { INotebookEditor, INotebookEditorCreationOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { Emitter } from 'vs/base/common/event';
import { INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { GroupIdentifier, GroupChangeKind } from 'vs/workbench/common/editor';
export class NotebookEditorWidgetService implements INotebookEditorService {
readonly _serviceBrand: undefined;
private _tokenPool = 1;
private readonly _disposables = new DisposableStore();
private readonly _notebookEditors = new Map<string, INotebookEditor>();
private readonly _decorationOptionProviders = new Map<string, INotebookDecorationRenderOptions>();
private readonly _onNotebookEditorAdd = new Emitter<INotebookEditor>();
private readonly _onNotebookEditorsRemove = new Emitter<INotebookEditor>();
readonly onDidAddNotebookEditor = this._onNotebookEditorAdd.event;
readonly onDidRemoveNotebookEditor = this._onNotebookEditorsRemove.event;
private readonly _borrowableEditors = new Map<number, ResourceMap<{ widget: NotebookEditorWidget, token: number | undefined; }>>();
constructor(
@IEditorGroupsService editorGroupService: IEditorGroupsService,
) {
const groupListener = new Map<number, IDisposable[]>();
const onNewGroup = (group: IEditorGroup) => {
const { id } = group;
const listeners: IDisposable[] = [];
listeners.push(group.onDidGroupChange(e => {
const widgets = this._borrowableEditors.get(group.id);
if (!widgets || e.kind !== GroupChangeKind.EDITOR_CLOSE) {
return;
}
const inputs = e.editor instanceof NotebookEditorInput ? [e.editor] : (isCompositeNotebookEditorInput(e.editor) ? e.editor.editorInputs : []);
inputs.forEach(input => {
const value = widgets.get(input.resource);
if (!value) {
return;
}
value.token = undefined;
this._disposeWidget(value.widget);
widgets.delete(input.resource);
value.widget = (<any>undefined); // unset the widget so that others that still hold a reference don't harm us
});
}));
listeners.push(group.onWillMoveEditor(e => {
if (e.editor instanceof NotebookEditorInput) {
this._freeWidget(e.editor, e.groupId, e.target);
}
if (isCompositeNotebookEditorInput(e.editor)) {
e.editor.editorInputs.forEach(input => {
this._freeWidget(input, e.groupId, e.target);
});
}
}));
groupListener.set(id, listeners);
};
this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup));
editorGroupService.whenReady.then(() => editorGroupService.groups.forEach(onNewGroup));
// group removed -> clean up listeners, clean up widgets
this._disposables.add(editorGroupService.onDidRemoveGroup(group => {
const listeners = groupListener.get(group.id);
if (listeners) {
listeners.forEach(listener => listener.dispose());
groupListener.delete(group.id);
}
const widgets = this._borrowableEditors.get(group.id);
this._borrowableEditors.delete(group.id);
if (widgets) {
for (const value of widgets.values()) {
value.token = undefined;
this._disposeWidget(value.widget);
}
}
}));
}
dispose() {
this._disposables.dispose();
this._onNotebookEditorAdd.dispose();
this._onNotebookEditorsRemove.dispose();
}
// --- group-based editor borrowing...
private _disposeWidget(widget: NotebookEditorWidget): void {
widget.onWillHide();
const domNode = widget.getDomNode();
widget.dispose();
domNode.remove();
}
private _freeWidget(input: NotebookEditorInput, sourceID: GroupIdentifier, targetID: GroupIdentifier): void {
const targetWidget = this._borrowableEditors.get(targetID)?.get(input.resource);
if (targetWidget) {
// not needed
return;
}
const widget = this._borrowableEditors.get(sourceID)?.get(input.resource);
if (!widget) {
throw new Error('no widget at source group');
}
this._borrowableEditors.get(sourceID)?.delete(input.resource);
widget.token = undefined;
let targetMap = this._borrowableEditors.get(targetID);
if (!targetMap) {
targetMap = new ResourceMap();
this._borrowableEditors.set(targetID, targetMap);
}
targetMap.set(input.resource, widget);
}
retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput, creationOptions?: INotebookEditorCreationOptions): IBorrowValue<NotebookEditorWidget> {
let value = this._borrowableEditors.get(group.id)?.get(input.resource);
if (!value) {
// NEW widget
const instantiationService = accessor.get(IInstantiationService);
const widget = instantiationService.createInstance(NotebookEditorWidget, creationOptions ?? getDefaultNotebookCreationOptions());
const token = this._tokenPool++;
value = { widget, token };
let map = this._borrowableEditors.get(group.id);
if (!map) {
map = new ResourceMap();
this._borrowableEditors.set(group.id, map);
}
map.set(input.resource, value);
} else {
// reuse a widget which was either free'ed before or which
// is simply being reused...
value.token = this._tokenPool++;
}
return this._createBorrowValue(value.token!, value);
}
private _createBorrowValue(myToken: number, widget: { widget: NotebookEditorWidget, token: number | undefined; }): IBorrowValue<NotebookEditorWidget> {
return {
get value() {
return widget.token === myToken ? widget.widget : undefined;
}
};
}
// --- editor management
addNotebookEditor(editor: INotebookEditor): void {
this._notebookEditors.set(editor.getId(), editor);
this._onNotebookEditorAdd.fire(editor);
}
removeNotebookEditor(editor: INotebookEditor): void {
if (this._notebookEditors.has(editor.getId())) {
this._notebookEditors.delete(editor.getId());
this._onNotebookEditorsRemove.fire(editor);
}
}
getNotebookEditor(editorId: string): INotebookEditor | undefined {
return this._notebookEditors.get(editorId);
}
listNotebookEditors(): readonly INotebookEditor[] {
return [...this._notebookEditors].map(e => e[1]);
}
// --- editor decorations
registerEditorDecorationType(key: string, options: INotebookDecorationRenderOptions): void {
if (!this._decorationOptionProviders.has(key)) {
this._decorationOptionProviders.set(key, options);
}
}
removeEditorDecorationType(key: string): void {
this._decorationOptionProviders.delete(key);
this.listNotebookEditors().forEach(editor => editor.removeEditorDecorations(key));
}
resolveEditorDecorationOptions(key: string): INotebookDecorationRenderOptions | undefined {
return this._decorationOptionProviders.get(key);
}
}