From 68ed2365bfffb22cf3e08c863f0cbb99715daa8b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 2 May 2018 06:52:20 +0200 Subject: [PATCH] grid - handle editor close properly as before --- .../parts/editor2/nextEditorGroupView.ts | 71 ++++++++++++++----- .../browser/parts/editor2/nextEditorPart.ts | 8 +-- .../common/editor/editorStacksModel.ts | 18 ++++- .../parts/editor/editorStacksModel.test.ts | 17 +++++ 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts b/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts index ca772e294dd..feb3dea7751 100644 --- a/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts @@ -38,7 +38,7 @@ import { RunOnceWorker } from 'vs/base/common/async'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; export interface IGroupsAccessor { - isOpenedInOtherGroup(editor: EditorInput): boolean; + getGroups(): NextEditorGroupView[]; } export class NextEditorGroupView extends Themable implements IView, INextEditorGroup { @@ -60,6 +60,12 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG private _onWillOpenEditor: Emitter = this._register(new Emitter()); get onWillOpenEditor(): Event { return this._onWillOpenEditor.event; } + private _onWillCloseEditor: Emitter = this._register(new Emitter()); + get onWillCloseEditor(): Event { return this._onWillCloseEditor.event; } + + private _onDidCloseEditor: Emitter = this._register(new Emitter()); + get onDidCloseEditor(): Event { return this._onDidCloseEditor.event; } + private _onDidOpenEditorFail: Emitter = this._register(new Emitter()); get onDidOpenEditorFail(): Event { return this._onDidOpenEditorFail.event; } @@ -100,7 +106,7 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG } else { this.group = this._register(instantiationService.createInstance(EditorGroup, '')); } - this.group.label = `Group <${this.group.id}>`; + this.group.label = `Group <${this.group.id}>`; // TODO@grid find a way to have a proper label this.disposedEditorsWorker = this._register(new RunOnceWorker(editors => this.handleDisposedEditors(editors), 0)); @@ -109,11 +115,15 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG } private registerListeners(): void { + + // Model Events this._register(this.group.onEditorsStructureChanged(() => this.updateContainer())); this._register(this.group.onEditorDirty(editor => this.pinEditor(editor))); this._register(this.group.onEditorOpened(editor => this.onEditorOpened(editor))); this._register(this.group.onEditorClosed(editor => this.onEditorClosed(editor))); this._register(this.group.onEditorDisposed(editor => this.onEditorDisposed(editor))); + + // Configuration Changes this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidChangeConfiguration(e))); } @@ -129,6 +139,16 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG } private onEditorClosed(event: EditorCloseEvent): void { + + // Before close + this._onWillCloseEditor.fire(event.editor); + + // Handle event + this.handleEditorClosed(event.editor); + + // After close + this._onDidCloseEditor.fire(event.editor); + /* __GDPR__ "editorClosed" : { "${include}": [ @@ -139,6 +159,23 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG this.telemetryService.publicLog('editorClosed', event.editor.getTelemetryDescriptor()); } + private handleEditorClosed(editor: EditorInput): void { + const editorsToClose = [editor]; + + // Include both sides of side by side editors when being closed and not opened multiple times + if (editor instanceof SideBySideEditorInput && !this.groupsAccessor.getGroups().some(groupView => groupView.group.contains(editor))) { + editorsToClose.push(editor.master, editor.details); + } + + // Close the editor when it is no longer open in any group including diff editors + editorsToClose.forEach(editorToClose => { + const resource = editorToClose ? editorToClose.getResource() : void 0; // prefer resource to not close right-hand side editors of a diff editor + if (!this.groupsAccessor.getGroups().some(groupView => groupView.group.contains(resource || editorToClose))) { + editorToClose.close(); + } + }); + } + private onEditorDisposed(editor: EditorInput): void { // To prevent race conditions, we handle disposed editors in our worker with a timeout @@ -247,10 +284,6 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG } } - contains(editor: EditorInput): boolean { - return this.group.contains(editor); - } - isEmpty(): boolean { return this.group.count === 0; } @@ -446,6 +479,8 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG private doCloseInactiveEditor(editor: EditorInput): void { this.group.closeEditor(editor); // Closing inactive editor is just a model update + + // TODO@grid need to update the title area control as well! } private handleDirty(editors: EditorInput[], ignoreIfOpenedInOtherGroup?: boolean): Thenable { @@ -463,10 +498,20 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG } private doHandleDirty(editor: EditorInput, ignoreIfOpenedInOtherGroup?: boolean): Thenable { - if (!editor || !editor.isDirty() || (ignoreIfOpenedInOtherGroup && this.isOpenedInOtherGroup(editor))) { + + // Return quickly if editor is not dirty + if (!editor.isDirty()) { return TPromise.as(false); // no veto } + // Return if editor is opened in other group and we are OK with it + if (ignoreIfOpenedInOtherGroup) { + const containedInOtherGroup = this.groupsAccessor.getGroups().some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)); + if (containedInOtherGroup) { + return TPromise.as(false); // no veto + } + } + // Switch to editor that we want to handle return this.openEditor(editor).then(() => { return editor.confirmSave().then(res => { @@ -503,18 +548,6 @@ export class NextEditorGroupView extends Themable implements IView, INextEditorG }); } - private isOpenedInOtherGroup(editor: EditorInput): boolean { - if (this.groupsAccessor.isOpenedInOtherGroup(editor)) { - return true; - } - - if (editor instanceof SideBySideEditorInput && this.groupsAccessor.isOpenedInOtherGroup(editor.master)) { - return true; // consider right-hand-side diff editors too - } - - return false; - } - //#endregion //#region other INextEditorGroup methods diff --git a/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts b/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts index c17978e9565..ce1111123a4 100644 --- a/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts @@ -228,11 +228,7 @@ export class NextEditorPart extends Part implements INextEditorGroupsService { } private doCreateGroupView(sourceView?: NextEditorGroupView): NextEditorGroupView { - const groupView = this.instantiationService.createInstance(NextEditorGroupView, sourceView, { - isOpenedInOtherGroup: editor => { - return this.groups.some(group => group !== groupView && group.contains(editor)); - } - }); + const groupView = this.instantiationService.createInstance(NextEditorGroupView, sourceView, { getGroups: () => this.groups }); // Keep in map this.groupViews.set(groupView.id, groupView); @@ -290,7 +286,7 @@ export class NextEditorPart extends Part implements INextEditorGroupsService { shutdown(): void { - // TODO@grid persist some UI state in the memento + // TODO@grid persist some UI state in the memento (note: a group can turn empty if none of the inputs can be serialized!) // Forward to all groups this.groupViews.forEach(group => group.shutdown()); diff --git a/src/vs/workbench/common/editor/editorStacksModel.ts b/src/vs/workbench/common/editor/editorStacksModel.ts index cc4f1a1242a..53388bf8f6a 100644 --- a/src/vs/workbench/common/editor/editorStacksModel.ts +++ b/src/vs/workbench/common/editor/editorStacksModel.ts @@ -601,9 +601,23 @@ export class EditorGroup extends Disposable implements IEditorGroup { return -1; } - public contains(editorOrResource: EditorInput | URI): boolean { + public contains(editorOrResource: EditorInput | URI): boolean; + public contains(editor: EditorInput, supportSideBySide?: boolean): boolean; + public contains(editorOrResource: EditorInput | URI, supportSideBySide?: boolean): boolean { if (editorOrResource instanceof EditorInput) { - return this.indexOf(editorOrResource) >= 0; + const index = this.indexOf(editorOrResource); + if (index >= 0) { + return true; + } + + if (supportSideBySide && editorOrResource instanceof SideBySideEditorInput) { + const index = this.indexOf(editorOrResource.master); + if (index >= 0) { + return true; + } + } + + return false; } const counter = this.mapResourceToEditorCount.get(editorOrResource); diff --git a/src/vs/workbench/test/browser/parts/editor/editorStacksModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorStacksModel.test.ts index 9c9bbcb4aee..9a24c7bebda 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorStacksModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorStacksModel.test.ts @@ -2016,4 +2016,21 @@ suite('Editor Stacks Model', () => { assert.equal(clone.isPinned(input3), false); assert.equal(clone.isActive(input3), true); }); + + test('Stack - contains() with diff editor support', function () { + const model = create(); + + const group1 = model.openGroup('group1'); + + const input1 = input(); + const input2 = input(); + + const diffInput = new DiffEditorInput('name', 'description', input1, input2); + + group1.openEditor(input2, { pinned: true, active: true }); + + assert.equal(group1.contains(input2), true); + assert.equal(group1.contains(diffInput), false); + assert.equal(group1.contains(diffInput, true), true); + }); }); \ No newline at end of file -- GitLab