未验证 提交 f14fa4d0 编写于 作者: J Johannes Rieken 提交者: GitHub

Merge pull request #100266 from microsoft/joh/notebook-inputs

Ref-count notebook models, rework notebook inputs
......@@ -6,9 +6,8 @@
import { coalesce, distinct } from 'vs/base/common/arrays';
import { Schemas } from 'vs/base/common/network';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { parse } from 'vs/base/common/marshalling';
import { basename, isEqual } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/resources';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { ITextModel, ITextBufferFactory, DefaultEndOfLine, ITextBuffer } from 'vs/editor/common/model';
......@@ -17,7 +16,7 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorOptions, ITextEditorOptions, IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -33,7 +32,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS
import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl';
import { CellKind, CellUri, NotebookDocumentBackupData, NotebookEditorPriority } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { IEditorGroup, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting';
......@@ -41,7 +40,6 @@ import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/custo
import { NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import { INotebookEditorModelResolverService, NotebookModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
// Editor Contribution
......@@ -83,7 +81,6 @@ class NotebookEditorFactory implements IEditorInputFactory {
resource: input.resource,
name: input.name,
viewType: input.viewType,
group: input.group
});
}
deserialize(instantiationService: IInstantiationService, raw: string) {
......@@ -97,13 +94,7 @@ class NotebookEditorFactory implements IEditorInputFactory {
return undefined;
}
// if we have two editors open with the same resource (in different editor groups), we should then create two different
// editor inputs, instead of `getOrCreate`.
const input = NotebookEditorInput.create(instantiationService, resource, name, viewType);
if (typeof data.group === 'number') {
input.updateGroup(data.group);
}
return input;
}
......@@ -147,14 +138,13 @@ function getFirstNotebookInfo(notebookService: INotebookService, uri: URI): Note
}
export class NotebookContribution extends Disposable implements IWorkbenchContribution {
private _resourceMapping = new ResourceMap<NotebookEditorInput>();
constructor(
@IEditorService private readonly editorService: IEditorService,
@INotebookService private readonly notebookService: INotebookService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IUndoRedoService undoRedoService: IUndoRedoService
@IUndoRedoService undoRedoService: IUndoRedoService,
) {
super();
......@@ -192,7 +182,9 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
};
});
},
open: (editor, options, group, context) => this.onEditorOpening(editor, options, group, context)
open: (editor, options, group) => {
return this.onEditorOpening2(editor, options, group);
}
}));
this._register(this.editorService.onDidVisibleEditorsChange(() => {
......@@ -214,22 +206,6 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
this.notebookService.updateActiveNotebookEditor(null);
}
}));
this._register(this.editorService.onDidCloseEditor(({ editor }) => {
if (!(editor instanceof NotebookEditorInput)) {
return;
}
if (!this.editorService.editors.some(other => (
other.resource === editor.resource
&& other instanceof NotebookEditorInput
&& other.viewType === editor.viewType
))) {
editor.clearTextModel();
}
editor.dispose();
}));
}
getUserAssociatedEditors(resource: URI) {
......@@ -251,54 +227,38 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
return this.notebookService.getContributedNotebookProviders(resource);
}
private onEditorOpening(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext): IOpenEditorOverride | undefined {
private onEditorOpening2(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined {
const id = typeof options?.override === 'string' ? options.override : undefined;
if (id === undefined && originalInput.isUntitled()) {
return;
return undefined;
}
if (originalInput instanceof NotebookEditorInput) {
if ((originalInput.group === group.id || originalInput.group === undefined) && (originalInput.viewType === id || typeof id !== 'string')) {
// No need to do anything
originalInput.updateGroup(group.id);
return {
override: this.editorService.openEditor(originalInput, new NotebookEditorOptions(options || {}).with({ override: false }), group)
};
} else {
// Create a copy of the input.
// Unlike normal editor inputs, we do not want to share custom editor inputs
// between multiple editors / groups.
const copiedInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource, originalInput.name, originalInput.viewType);
copiedInput.updateGroup(group.id);
if (context === OpenEditorContext.MOVE_EDITOR) {
// transfer ownership of editor widget
const widgetRef = NotebookRegistry.getNotebookEditorWidget(originalInput);
if (widgetRef) {
NotebookRegistry.releaseNotebookEditorWidget(originalInput);
NotebookRegistry.claimNotebookEditorWidget(copiedInput, widgetRef);
}
}
return {
override: this.editorService.openEditor(copiedInput, new NotebookEditorOptions(options || {}).with({ override: false }), group)
};
}
if (!originalInput.resource) {
return undefined;
}
let resource = originalInput.resource;
if (!resource) {
if (originalInput instanceof NotebookEditorInput) {
return undefined;
}
let notebookUri: URI = originalInput.resource;
let cellOptions: IResourceEditorInput | undefined;
const data = CellUri.parse(originalInput.resource);
if (data) {
notebookUri = data.notebook;
cellOptions = { resource: originalInput.resource, options };
}
if (id === undefined) {
const existingEditors = group.editors.filter(editor => editor.resource && isEqual(editor.resource, resource) && !(editor instanceof NotebookEditorInput));
const existingEditors = group.editors.filter(editor => editor.resource && isEqual(editor.resource, notebookUri) && !(editor instanceof NotebookEditorInput));
if (existingEditors.length) {
return undefined;
}
const userAssociatedEditors = this.getUserAssociatedEditors(resource);
const userAssociatedEditors = this.getUserAssociatedEditors(notebookUri);
const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.viewType));
if (userAssociatedEditors.length && !notebookEditor.length) {
......@@ -309,46 +269,18 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
// user might pick a notebook editor
const associatedEditors = distinct([
...this.getUserAssociatedNotebookEditors(resource),
...this.getContributedEditors(resource)
...this.getUserAssociatedNotebookEditors(notebookUri),
...this.getContributedEditors(notebookUri)
], editor => editor.id).filter(editor => editor.priority === NotebookEditorPriority.default);
if (!associatedEditors.length) {
// there is no notebook editor contribution which is enabled by default
return;
}
} else {
const existingEditors = group.editors.filter(editor => editor.resource && isEqual(editor.resource, resource) && (editor instanceof NotebookEditorInput) && editor.viewType === id);
if (existingEditors.length) {
// switch to this cell
return { override: this.editorService.openEditor(existingEditors[0], new NotebookEditorOptions(options || {}).with({ override: false }), group) };
}
}
let info: NotebookProviderInfo | undefined;
const data = CellUri.parse(resource);
if (data) {
const infos = this.getContributedEditors(data.notebook);
if (infos.length) {
const info = id === undefined ? infos[0] : (infos.find(info => info.id === id) || infos[0]);
// cell-uri -> open (container) notebook
const name = basename(data.notebook);
let input = this._resourceMapping.get(data.notebook);
if (!input || input.isDisposed()) {
input = NotebookEditorInput.create(this.instantiationService, data.notebook, name, info.id);
this._resourceMapping.set(data.notebook, input);
}
input.updateGroup(group.id);
return { override: this.editorService.openEditor(input, new NotebookEditorOptions({ ...options, forceReload: true, cellOptions: { resource, options } }), group) };
return undefined;
}
}
const infos = this.notebookService.getContributedNotebookProviders(resource);
info = id === undefined ? infos[0] : infos.find(info => info.id === id);
const infos = this.notebookService.getContributedNotebookProviders(notebookUri);
let info = infos.find(info => !id || info.id === id);
if (!info && id !== undefined) {
info = this.notebookService.getContributedNotebookProvider(id);
......@@ -358,20 +290,19 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
return undefined;
}
const input = NotebookEditorInput.create(this.instantiationService, resource, originalInput.getName(), info.id);
input.updateGroup(group.id);
this._resourceMapping.set(resource, input);
/**
* Scenario: we are reopening a file editor input which is pinned, we should open in a new editor tab.
*/
let index = undefined;
if (group.activeEditor === originalInput && isEqual(originalInput.resource, resource)) {
if (group.activeEditor === originalInput && isEqual(originalInput.resource, notebookUri)) {
const originalEditorIndex = group.getIndexOfEditor(originalInput);
index = group.isPinned(originalInput) ? originalEditorIndex + 1 : originalEditorIndex;
}
return { override: this.editorService.openEditor(input, new NotebookEditorOptions(options || {}).with({ override: false, index }), group) };
const notebookInput = NotebookEditorInput.create(this.instantiationService, notebookUri, originalInput.getName(), info.id);
const notebookOptions = new NotebookEditorOptions({ ...options, cellOptions, override: false, index });
return { override: this.editorService.openEditor(notebookInput, notebookOptions, group) };
}
}
......@@ -384,6 +315,7 @@ class CellContentProvider implements ITextModelContentProvider {
@IModelService private readonly _modelService: IModelService,
@IModeService private readonly _modeService: IModeService,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService,
) {
this._registration = textModelService.registerTextModelContentProvider(CellUri.scheme, this);
}
......@@ -407,12 +339,10 @@ class CellContentProvider implements ITextModelContentProvider {
return null;
}
const editorModel = await this._notebookService.modelManager.resolve(data.notebook, info.id);
if (!editorModel) {
return null;
}
const ref = await this._notebookModelResolverService.resolve(data.notebook, info.id);
let result: ITextModel | null = null;
for (let cell of editorModel.notebook.cells) {
for (let cell of ref.object.notebook.cells) {
if (cell.uri.toString() === resource.toString()) {
const bufferFactory: ITextBufferFactory = {
create: (defaultEOL) => {
......@@ -425,15 +355,23 @@ class CellContentProvider implements ITextModelContentProvider {
}
};
const language = cell.cellKind === CellKind.Markdown ? this._modeService.create('markdown') : (cell.language ? this._modeService.create(cell.language) : this._modeService.createByFilepathOrFirstLine(resource, cell.textBuffer.getLineContent(1)));
return this._modelService.createModel(
result = this._modelService.createModel(
bufferFactory,
language,
resource
);
break;
}
}
return null;
if (result) {
const once = result.onWillDispose(() => {
once.dispose();
ref.dispose();
});
}
return result;
}
}
......
......@@ -17,11 +17,12 @@ import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/noteb
import { INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { INotebookEditorWidgetService, IBorrowValue } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetService';
import { localize } from 'vs/nls';
const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';
......@@ -29,7 +30,7 @@ export class NotebookEditor extends BaseEditor {
static readonly ID: string = 'workbench.editor.notebook';
private editorMemento: IEditorMemento<INotebookEditorViewState>;
private readonly groupListener = this._register(new MutableDisposable());
private _widget?: NotebookEditorWidget;
private _widget: IBorrowValue<NotebookEditorWidget> = { value: undefined };
private _rootElement!: HTMLElement;
private dimension: DOM.Dimension | null = null;
private _widgetDisposableStore: DisposableStore = new DisposableStore();
......@@ -43,10 +44,10 @@ export class NotebookEditor extends BaseEditor {
@IStorageService storageService: IStorageService,
@IEditorService private readonly editorService: IEditorService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@INotificationService private readonly notificationService: INotificationService) {
@INotificationService private readonly notificationService: INotificationService,
@INotebookEditorWidgetService private readonly notebookWidgetService: INotebookEditorWidgetService,
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
// this._widget = this.instantiationService.createInstance(NotebookEditorWidget);
this.editorMemento = this.getEditorMemento<INotebookEditorViewState>(editorGroupService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
}
......@@ -55,14 +56,14 @@ export class NotebookEditor extends BaseEditor {
set viewModel(newModel: NotebookViewModel | undefined) {
if (this._widget) {
this._widget.viewModel = newModel;
if (this._widget.value) {
this._widget.value.viewModel = newModel;
this._onDidChangeModel.fire();
}
}
get viewModel() {
return this._widget?.viewModel;
return this._widget.value?.viewModel;
}
get minimumWidth(): number { return 375; }
......@@ -84,28 +85,26 @@ export class NotebookEditor extends BaseEditor {
this._rootElement = DOM.append(parent, DOM.$('.notebook-editor'));
// this._widget.createEditor();
this._register(this.onDidFocus(() => this._widget?.updateEditorFocus()));
this._register(this.onDidBlur(() => this._widget?.updateEditorFocus()));
this._register(this.onDidFocus(() => this._widget.value?.updateEditorFocus()));
this._register(this.onDidBlur(() => this._widget.value?.updateEditorFocus()));
}
getDomNode() {
return this._rootElement;
}
getControl() {
return this._widget;
getControl(): NotebookEditorWidget | undefined {
return this._widget.value;
}
onWillHide() {
if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) {
if (this.input instanceof NotebookEditorInput) {
this.saveEditorViewState(this.input);
}
if (this.input && NotebookRegistry.getNotebookEditorWidget(this.input as NotebookEditorInput) === this._widget) {
if (this.input && this._widget.value) {
// the widget is not transfered to other editor inputs
this._widget?.onWillHide();
this._widget.value.onWillHide();
}
super.onWillHide();
}
......@@ -127,69 +126,42 @@ export class NotebookEditor extends BaseEditor {
focus() {
super.focus();
this._widget?.focus();
this._widget.value?.focus();
}
async setInput(input: NotebookEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
const group = this.group!;
if (this.input instanceof NotebookEditorInput) {
if (!this.input.isDisposed()) {
// set a new input, let's hide previous input
this.saveEditorViewState(this.input as NotebookEditorInput);
this._widget?.onWillHide();
}
// set a new input, let's hide previous input
this.saveEditorViewState(this.input as NotebookEditorInput);
}
await super.setInput(input, options, token);
// input attached
Event.once(input.onDispose)(() => {
// make sure the editor widget is removed from the view
const existingEditorWidgetForInput = NotebookRegistry.getNotebookEditorWidget(input as NotebookEditorInput);
if (existingEditorWidgetForInput) {
// the editor widget is only referenced by the editor input
// clear its state
existingEditorWidgetForInput?.onWillHide();
existingEditorWidgetForInput?.getDomNode().remove();
existingEditorWidgetForInput?.dispose();
NotebookRegistry.releaseNotebookEditorWidget(input as NotebookEditorInput);
}
});
this._widgetDisposableStore.clear();
const existingEditorWidgetForInput = NotebookRegistry.getNotebookEditorWidget(input);
if (existingEditorWidgetForInput) {
// hide previous widget
if (NotebookRegistry.getNotebookEditorWidget(this.input! as NotebookEditorInput) === this._widget) {
// the widet is not transfered to other editor inputs
this._widget?.onWillHide();
}
// previous widget is then detached
// set the new one
this._widget = existingEditorWidgetForInput;
NotebookRegistry.claimNotebookEditorWidget(input, this._widget);
} else {
// hide current widget
this._widget?.onWillHide();
// create a new widget
this._widget = this.instantiationService.createInstance(NotebookEditorWidget);
this._widget.createEditor();
NotebookRegistry.claimNotebookEditorWidget(input, this._widget);
// there currently is a widget which we still own so
// we need to hide it before getting a new widget
if (this._widget.value) {
this._widget.value.onWillHide();
}
this._widget = this.instantiationService.invokeFunction(this.notebookWidgetService.retrieveWidget, group, input);
if (this.dimension) {
this._widget.layout(this.dimension, this._rootElement);
this._widget.value!.layout(this.dimension, this._rootElement);
}
const model = await input.resolve(this._widget!.getId());
const model = await input.resolve(this._widget.value!.getId());
if (model === null) {
this.notificationService.prompt(
Severity.Error,
`Cannot open resource with notebook editor type '${input.viewType}', please check if you have the right extension installed or enabled.`,
localize('fail.noEditor', "Cannot open resource with notebook editor type '${input.viewType}', please check if you have the right extension installed or enabled."),
[{
label: 'Reopen file with VS Code standard text editor',
label: localize('fail.reOpen', "Reopen file with VS Code standard text editor"),
run: async () => {
const fileEditorInput = this.editorService.createEditorInput({ resource: input.resource, forceFile: true });
const textOptions: IEditorOptions | ITextEditorOptions = options ? { ...options, override: false } : { override: false };
......@@ -202,26 +174,26 @@ export class NotebookEditor extends BaseEditor {
const viewState = this.loadTextEditorViewState(input);
await this._widget.setModel(model.notebook, viewState, options);
this._widgetDisposableStore.add(this._widget.onDidFocus(() => this._onDidFocusWidget.fire()));
await this._widget.value!.setModel(model.notebook, viewState, options);
this._widgetDisposableStore.add(this._widget.value!.onDidFocus(() => this._onDidFocusWidget.fire()));
if (this.editorGroupService instanceof EditorPart) {
this._widgetDisposableStore.add(this.editorGroupService.createEditorDropTarget(this._widget.getDomNode(), {
this._widgetDisposableStore.add(this.editorGroupService.createEditorDropTarget(this._widget.value!.getDomNode(), {
groupContainsPredicate: (group) => this.group?.id === group.group.id
}));
}
}
clearInput(): void {
const existingEditorWidgetForInput = NotebookRegistry.getNotebookEditorWidget(this.input as NotebookEditorInput);
existingEditorWidgetForInput?.onWillHide();
this._widget = undefined;
if (this._widget.value) {
this._widget.value.onWillHide();
}
super.clearInput();
}
private saveEditorViewState(input: NotebookEditorInput): void {
if (this.group && this._widget) {
const state = this._widget.getEditorViewState();
if (this.group && this._widget.value) {
const state = this._widget.value.getEditorViewState();
this.editorMemento.saveEditorState(this.group, input.resource, state);
}
}
......@@ -239,11 +211,11 @@ export class NotebookEditor extends BaseEditor {
DOM.toggleClass(this._rootElement, 'narrow-width', dimension.width < 600);
this.dimension = dimension;
if (this._input === undefined || this._widget === undefined) {
if (!this._widget.value || !(this._input instanceof NotebookEditorInput)) {
return;
}
if (this._input.resource?.toString() !== this._widget?.viewModel?.uri.toString() && this._widget?.viewModel) {
if (this._input.resource.toString() !== this._widget.value.viewModel?.uri.toString() && this._widget.value?.viewModel) {
// input and widget mismatch
// this happens when
// 1. open document A, pin the document
......@@ -253,7 +225,7 @@ export class NotebookEditor extends BaseEditor {
return;
}
this._widget?.layout(this.dimension, this._rootElement);
this._widget.value.layout(this.dimension, this._rootElement);
}
protected saveState(): void {
......@@ -280,4 +252,3 @@ export class NotebookEditor extends BaseEditor {
};
}
}
......@@ -9,10 +9,10 @@ import { URI } from 'vs/base/common/uri';
import { isEqual, basename } from 'vs/base/common/resources';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { NotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
let NOTEBOOK_EDITOR_INPUT_HANDLE = 0;
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { IReference } from 'vs/base/common/lifecycle';
import { INotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
interface NotebookEditorInputOptions {
startDirty?: boolean;
......@@ -24,30 +24,19 @@ export class NotebookEditorInput extends EditorInput {
}
static readonly ID: string = 'workbench.input.notebook';
private textModel: NotebookEditorModel | null = null;
private _group: GroupIdentifier | undefined;
public get group(): GroupIdentifier | undefined {
return this._group;
}
public updateGroup(group: GroupIdentifier): void {
this._group = group;
}
private _textModel: IReference<INotebookEditorModel> | null = null;
private _defaultDirtyState: boolean = false;
readonly id: number = NOTEBOOK_EDITOR_INPUT_HANDLE++;
constructor(
public resource: URI,
public name: string,
public readonly resource: URI,
public readonly name: string,
public readonly viewType: string | undefined,
public readonly options: NotebookEditorInputOptions,
@INotebookService private readonly notebookService: INotebookService,
@INotebookEditorModelResolverService private readonly notebookModelResolverService: INotebookEditorModelResolverService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
// @IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
......@@ -63,18 +52,17 @@ export class NotebookEditorInput extends EditorInput {
}
isDirty() {
if (!this.textModel) {
if (!this._textModel) {
return !!this._defaultDirtyState;
}
return this.textModel?.isDirty() || false;
return this._textModel.object.isDirty();
}
isReadonly() {
return false;
}
public isSaving(): boolean {
isSaving(): boolean {
if (this.isUntitled()) {
return false; // untitled is never saving automatically
}
......@@ -91,8 +79,8 @@ export class NotebookEditorInput extends EditorInput {
}
async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
if (this.textModel) {
await this.textModel.save();
if (this._textModel) {
await this._textModel.object.save();
return this;
}
......@@ -100,17 +88,17 @@ export class NotebookEditorInput extends EditorInput {
}
async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
if (!this.textModel) {
if (!this._textModel) {
return undefined;
}
const dialogPath = this.textModel.resource;
const dialogPath = this._textModel.object.resource;
const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems);
if (!target) {
return undefined; // save cancelled
}
if (!await this.textModel.saveAs(target)) {
if (!await this._textModel.object.saveAs(target)) {
return undefined;
}
......@@ -119,45 +107,47 @@ export class NotebookEditorInput extends EditorInput {
// called when users rename a notebook document
rename(group: GroupIdentifier, target: URI): IMoveResult | undefined {
if (this.textModel) {
if (this._textModel) {
const contributedNotebookProviders = this.notebookService.getContributedNotebookProviders(target);
if (contributedNotebookProviders.find(provider => provider.id === this.textModel!.viewType)) {
if (contributedNotebookProviders.find(provider => provider.id === this._textModel!.object.viewType)) {
return this._move(group, target);
}
}
return undefined;
}
_move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
private _move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
const editorInput = NotebookEditorInput.create(this.instantiationService, newResource, basename(newResource), this.viewType);
return { editor: editorInput };
}
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
if (this.textModel) {
await this.textModel.revert(options);
if (this._textModel) {
await this._textModel.object.revert(options);
}
return;
}
async resolve(editorId?: string): Promise<NotebookEditorModel | null> {
async resolve(editorId?: string): Promise<INotebookEditorModel | null> {
if (!await this.notebookService.canResolve(this.viewType!)) {
return null;
}
this.textModel = await this.notebookService.modelManager.resolve(this.resource, this.viewType!, editorId);
if (!this._textModel) {
this._textModel = await this.notebookModelResolverService.resolve(this.resource, this.viewType!, editorId);
this._register(this.textModel.onDidChangeDirty(() => {
this._onDidChangeDirty.fire();
}));
this._register(this._textModel.object.onDidChangeDirty(() => {
this._onDidChangeDirty.fire();
}));
if (this.textModel.isDirty()) {
this._onDidChangeDirty.fire();
if (this._textModel.object.isDirty()) {
this._onDidChangeDirty.fire();
}
}
return this.textModel;
return this._textModel.object;
}
matches(otherInput: unknown): boolean {
......@@ -171,14 +161,11 @@ export class NotebookEditorInput extends EditorInput {
return false;
}
clearTextModel() {
if (this.textModel) {
this.notebookService.destoryNotebookDocument(this.textModel!.notebook.viewType, this.textModel!.notebook);
this.textModel.dispose();
}
}
dispose() {
if (this._textModel) {
this._textModel.dispose();
this._textModel = null;
}
super.dispose();
}
}
......@@ -98,6 +98,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
public get onDidFocus(): Event<any> { return this._onDidFocusWidget.event; }
private _cellContextKeyManager: CellContextKeyManager | null = null;
private _isVisible = false;
private readonly _uuid = generateUuid();
get isDisposed() {
return this._isDisposed;
......@@ -130,7 +131,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.notebookService.addNotebookEditor(this);
}
private _uuid = generateUuid();
public getId(): string {
return this._uuid;
}
......
/*---------------------------------------------------------------------------------------------
* 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 { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IEditorGroupsService, IEditorGroup, GroupChangeKind, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IInstantiationService, createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export const INotebookEditorWidgetService = createDecorator<INotebookEditorWidgetService>('INotebookEditorWidgetService');
export interface IBorrowValue<T> {
readonly value: T | undefined;
}
export interface INotebookEditorWidgetService {
_serviceBrand: undefined;
retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput): IBorrowValue<NotebookEditorWidget>;
}
class NotebookEditorWidgetService implements INotebookEditorWidgetService {
readonly _serviceBrand: undefined;
private _tokenPool = 1;
private readonly _notebookWidgets = new Map<number, ResourceMap<{ widget: NotebookEditorWidget, token: number | undefined }>>();
private readonly _disposables = new DisposableStore();
constructor(
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService,
) {
const groupListener = new Map<number, IDisposable>();
const onNewGroup = (group: IEditorGroup) => {
const { id } = group;
const listener = group.onDidGroupChange(e => {
const widgets = this._notebookWidgets.get(group.id);
if (!widgets || e.kind !== GroupChangeKind.EDITOR_CLOSE || !(e.editor instanceof NotebookEditorInput)) {
return;
}
const value = widgets.get(e.editor.resource);
if (!value) {
return;
}
value.token = undefined;
this._disposeWidget(value.widget);
widgets.delete(e.editor.resource);
});
groupListener.set(id, listener);
};
this._disposables.add(editorGroupService.onDidAddGroup(onNewGroup));
editorGroupService.groups.forEach(onNewGroup);
// group removed -> clean up listeners, clean up widgets
this._disposables.add(editorGroupService.onDidRemoveGroup(group => {
const listener = groupListener.get(group.id);
if (listener) {
listener.dispose();
groupListener.delete(group.id);
}
const widgets = this._notebookWidgets.get(group.id);
this._notebookWidgets.delete(group.id);
if (widgets) {
for (let value of widgets.values()) {
value.token = undefined;
this._disposeWidget(value.widget);
}
}
}));
// HACK
// we use the open override to spy on tab movements because that's the only
// way to do that...
this._disposables.add(editorService.overrideOpenEditor({
open: (input, _options, group, context) => {
if (input instanceof NotebookEditorInput && context === OpenEditorContext.MOVE_EDITOR) {
// when moving a notebook editor we release it from its current tab and we
// "place" it into its future slot so that the editor can pick it up from there
this._freeWidget(input, editorGroupService.activeGroup, group);
}
return undefined;
}
}));
}
private _disposeWidget(widget: NotebookEditorWidget): void {
widget.onWillHide();
widget.getDomNode().remove();
widget.dispose();
}
private _freeWidget(input: NotebookEditorInput, source: IEditorGroup, target: IEditorGroup): void {
const targetWidget = this._notebookWidgets.get(target.id)?.get(input.resource);
if (targetWidget) {
// not needed
return;
}
const widget = this._notebookWidgets.get(source.id)?.get(input.resource);
if (!widget) {
throw new Error('no widget at source group');
}
this._notebookWidgets.get(source.id)?.delete(input.resource);
widget.token = undefined;
let targetMap = this._notebookWidgets.get(target.id);
if (!targetMap) {
targetMap = new ResourceMap();
this._notebookWidgets.set(target.id, targetMap);
}
targetMap.set(input.resource, widget);
}
retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput): IBorrowValue<NotebookEditorWidget> {
let value = this._notebookWidgets.get(group.id)?.get(input.resource);
if (!value) {
// NEW widget
const instantiationService = accessor.get(IInstantiationService);
const widget = instantiationService.createInstance(NotebookEditorWidget);
widget.createEditor();
const token = this._tokenPool++;
value = { widget, token };
let map = this._notebookWidgets.get(group.id);
if (!map) {
map = new ResourceMap();
this._notebookWidgets.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;
}
};
}
}
registerSingleton(INotebookEditorWidgetService, NotebookEditorWidgetService, true);
......@@ -6,8 +6,6 @@
import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { BrandedService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
export type IOutputTransformCtor = IConstructorSignature1<INotebookEditor, IOutputTransformContribution>;
......@@ -17,56 +15,16 @@ export interface IOutputTransformDescription {
ctor: IOutputTransformCtor;
}
export namespace NotebookRegistry {
export function getOutputTransformContributions(): IOutputTransformDescription[] {
return NotebookRegistryImpl.INSTANCE.getNotebookOutputTransform();
}
export function claimNotebookEditorWidget(editorInput: NotebookEditorInput, widget: NotebookEditorWidget) {
NotebookRegistryImpl.INSTANCE.claimNotebookEditorWidget(editorInput, widget);
}
export function releaseNotebookEditorWidget(editorInput: NotebookEditorInput) {
NotebookRegistryImpl.INSTANCE.releaseNotebookEditorWidget(editorInput);
}
export function getNotebookEditorWidget(editorInput: NotebookEditorInput): NotebookEditorWidget | undefined {
return NotebookRegistryImpl.INSTANCE.getNotebookEditorWidget(editorInput);
}
}
export function registerOutputTransform<Services extends BrandedService[]>(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void {
NotebookRegistryImpl.INSTANCE.registerOutputTransform(id, kind, ctor);
}
class NotebookRegistryImpl {
export const NotebookRegistry = new class NotebookRegistryImpl {
static readonly INSTANCE = new NotebookRegistryImpl();
private readonly outputTransforms: IOutputTransformDescription[];
private readonly notebookEditorWidgetOwnership = new Map<NotebookEditorInput, NotebookEditorWidget>();
constructor() {
this.outputTransforms = [];
}
readonly outputTransforms: IOutputTransformDescription[] = [];
registerOutputTransform<Services extends BrandedService[]>(id: string, kind: CellOutputKind, ctor: { new(editor: INotebookEditor, ...services: Services): IOutputTransformContribution }): void {
this.outputTransforms.push({ id: id, kind: kind, ctor: ctor as IOutputTransformCtor });
}
getNotebookOutputTransform(): IOutputTransformDescription[] {
getOutputTransformContributions(): IOutputTransformDescription[] {
return this.outputTransforms.slice(0);
}
claimNotebookEditorWidget(editorInput: NotebookEditorInput, widget: NotebookEditorWidget) {
this.notebookEditorWidgetOwnership.set(editorInput, widget);
}
releaseNotebookEditorWidget(editorInput: NotebookEditorInput) {
this.notebookEditorWidgetOwnership.delete(editorInput);
}
getNotebookEditorWidget(editorInput: NotebookEditorInput) {
return this.notebookEditorWidgetOwnership.get(editorInput);
}
}
};
......@@ -5,7 +5,6 @@
import * as nls from 'vs/nls';
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { URI, UriComponents } from 'vs/base/common/uri';
import { notebookProviderExtensionPoint, notebookRendererExtensionPoint, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { NotebookProviderInfo, NotebookEditorDescriptor } from 'vs/workbench/contrib/notebook/common/notebookProvider';
......@@ -19,7 +18,6 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEditorService, ICustomEditorViewTypesHandler, ICustomEditorInfo } from 'vs/workbench/services/editor/common/editorService';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/common/notebookService';
import * as glob from 'vs/base/common/glob';
import { basename } from 'vs/base/common/path';
......@@ -179,7 +177,6 @@ export class NotebookService extends Disposable implements INotebookService, ICu
onDidChangeKernels: Event<void> = this._onDidChangeKernels.event;
private cutItems: NotebookCellTextModel[] | undefined;
modelManager: NotebookEditorModelManager;
private _displayOrder: { userOrder: string[], defaultOrder: string[] } = Object.create(null);
constructor(
......@@ -187,13 +184,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IStorageService private readonly storageService: IStorageService
) {
super();
this.modelManager = this.instantiationService.createInstance(NotebookEditorModelManager);
this._register(this.modelManager);
this.notebookProviderInfoStore = new NotebookProviderInfoStore(this.storageService);
this._register(this.notebookProviderInfoStore);
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IRenderOutput, CellOutputKind, IErrorOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import * as DOM from 'vs/base/browser/dom';
import { RGBA, Color } from 'vs/base/common/color';
import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
......@@ -45,7 +45,7 @@ class ErrorTransform implements IOutputTransformContribution {
}
}
registerOutputTransform('notebook.output.error', CellOutputKind.Error, ErrorTransform);
NotebookRegistry.registerOutputTransform('notebook.output.error', CellOutputKind.Error, ErrorTransform);
/**
* @param text The content to stylize.
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IRenderOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import * as DOM from 'vs/base/browser/dom';
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { isArray } from 'vs/base/common/types';
......@@ -225,7 +225,7 @@ class RichRenderer implements IOutputTransformContribution {
}
}
registerOutputTransform('notebook.output.rich', CellOutputKind.Rich, RichRenderer);
NotebookRegistry.registerOutputTransform('notebook.output.rich', CellOutputKind.Rich, RichRenderer);
export function getOutputSimpleEditorOptions(): IEditorOptions {
......
......@@ -5,7 +5,7 @@
import * as DOM from 'vs/base/browser/dom';
import { IRenderOutput, CellOutputKind, IStreamOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { registerOutputTransform } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry';
import { INotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
class StreamRenderer implements IOutputTransformContribution {
......@@ -28,4 +28,4 @@ class StreamRenderer implements IOutputTransformContribution {
}
}
registerOutputTransform('notebook.output.stream', CellOutputKind.Text, StreamRenderer);
NotebookRegistry.registerOutputTransform('notebook.output.stream', CellOutputKind.Text, StreamRenderer);
......@@ -7,9 +7,6 @@ import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor';
import { Emitter, Event } from 'vs/base/common/event';
import { INotebookEditorModel, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { URI } from 'vs/base/common/uri';
import { IWorkingCopyService, IWorkingCopy, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService';
......@@ -222,119 +219,3 @@ export class NotebookEditorModel extends EditorModel implements IWorkingCopy, IN
return true;
}
}
export class NotebookEditorModelManager extends Disposable implements INotebookEditorModelManager {
private readonly mapResourceToModel = new ResourceMap<NotebookEditorModel>();
private readonly mapResourceToModelListeners = new ResourceMap<IDisposable>();
private readonly mapResourceToDisposeListener = new ResourceMap<IDisposable>();
private readonly mapResourceToPendingModelLoaders = new ResourceMap<Promise<NotebookEditorModel>>();
// private readonly modelLoadQueue = this._register(new ResourceQueue());
get models(): NotebookEditorModel[] {
return [...this.mapResourceToModel.values()];
}
constructor(
@IInstantiationService readonly instantiationService: IInstantiationService
) {
super();
}
async resolve(resource: URI, viewType: string, editorId?: string): Promise<NotebookEditorModel> {
// Return early if model is currently being loaded
const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource);
if (pendingLoad) {
return pendingLoad;
}
let modelPromise: Promise<NotebookEditorModel>;
let model = this.get(resource);
// let didCreateModel = false;
// Model exists
if (model) {
// if (options?.reload) {
// } else {
modelPromise = Promise.resolve(model);
// }
}
// Model does not exist
else {
// didCreateModel = true;
const newModel = model = this.instantiationService.createInstance(NotebookEditorModel, resource, viewType);
modelPromise = model.load({ editorId });
this.registerModel(newModel);
}
// Store pending loads to avoid race conditions
this.mapResourceToPendingModelLoaders.set(resource, modelPromise);
// Make known to manager (if not already known)
this.add(resource, model);
// dispose and bind new listeners
try {
const resolvedModel = await modelPromise;
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
return resolvedModel;
} catch (error) {
// Free resources of this invalid model
if (model) {
model.dispose();
}
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
throw error;
}
}
add(resource: URI, model: NotebookEditorModel): void {
const knownModel = this.mapResourceToModel.get(resource);
if (knownModel === model) {
return; // already cached
}
// dispose any previously stored dispose listener for this resource
const disposeListener = this.mapResourceToDisposeListener.get(resource);
if (disposeListener) {
disposeListener.dispose();
}
// store in cache but remove when model gets disposed
this.mapResourceToModel.set(resource, model);
this.mapResourceToDisposeListener.set(resource, model.onDispose(() => this.remove(resource)));
}
remove(resource: URI): void {
this.mapResourceToModel.delete(resource);
const disposeListener = this.mapResourceToDisposeListener.get(resource);
if (disposeListener) {
dispose(disposeListener);
this.mapResourceToDisposeListener.delete(resource);
}
const modelListener = this.mapResourceToModelListeners.get(resource);
if (modelListener) {
dispose(modelListener);
this.mapResourceToModelListeners.delete(resource);
}
}
private registerModel(model: NotebookEditorModel): void {
}
get(resource: URI): NotebookEditorModel | undefined {
return this.mapResourceToModel.get(resource);
}
}
......@@ -12,7 +12,6 @@ import { INotebookTextModel, INotebookRendererInfo, NotebookDocumentMetadata, IC
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CancellationToken } from 'vs/base/common/cancellation';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { INotebookEditorModelManager } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
export const INotebookService = createDecorator<INotebookService>('notebookService');
......@@ -32,7 +31,6 @@ export interface IMainNotebookController {
export interface INotebookService {
readonly _serviceBrand: undefined;
modelManager: INotebookEditorModelManager;
canResolve(viewType: string): Promise<boolean>;
onDidChangeActiveEditor: Event<string | null>;
onDidChangeVisibleEditors: Event<string[]>;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册