diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index eacce92ae85..80900e4c5ac 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -422,7 +422,7 @@ export class CloseEditorAction extends Action { // Get Top Editor at Position let visibleEditors = this.editorService.getVisibleEditors(); if (visibleEditors[position]) { - input = visibleEditors[position].input; + input = visibleEditors[position].input; } } @@ -555,7 +555,7 @@ export class CloseOtherEditorsInGroupAction extends Action { const active = this.editorService.getActiveEditor(); if (active) { position = typeof position === 'number' ? position : active.position; - input = input ? input : active.input; + input = input ? input : active.input; } if (typeof position === 'number' && input) { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index f9547dd3518..a5fd03d015f 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -38,7 +38,7 @@ import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollect import {IMessageService, IMessageWithAction, Severity} from 'vs/platform/message/common/message'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IProgressService} from 'vs/platform/progress/common/progress'; -import {EditorStacksModel, EditorGroup} from 'vs/workbench/common/editor/editorStacksModel'; +import {EditorStacksModel, EditorGroup, IEditorIdentifier} from 'vs/workbench/common/editor/editorStacksModel'; class ProgressMonitor { @@ -72,11 +72,10 @@ export class EditorPart extends Part implements IEditorPart { private dimension: Dimension; private sideBySideControl: ISideBySideEditorControl; private memento: any; - private stacksModel: EditorStacksModel; + private stacks: EditorStacksModel; // The following data structures are partitioned into array of Position as provided by Services.POSITION array private visibleInputs: EditorInput[]; - private visibleInputListeners: Function[]; private visibleEditors: BaseEditor[]; private visibleEditorListeners: Function[][]; private instantiatedEditors: BaseEditor[][]; @@ -85,7 +84,7 @@ export class EditorPart extends Part implements IEditorPart { private mapEditorCreationPromiseToEditor: { [editorId: string]: TPromise; }[]; private editorOpenToken: number[]; private editorSetInputErrorCounter: number[]; - private pendingEditorInputsToClose: { input: EditorInput; position: Position }[]; + private pendingEditorInputsToClose: IEditorIdentifier[]; private pendingEditorInputCloseTimeout: number; constructor( @@ -100,7 +99,6 @@ export class EditorPart extends Part implements IEditorPart { super(id); this.visibleInputs = []; - this.visibleInputListeners = []; this.visibleEditors = []; this.editorOpenToken = arrays.fill(POSITIONS.length, () => 0); @@ -116,13 +114,16 @@ export class EditorPart extends Part implements IEditorPart { this.pendingEditorInputsToClose = []; this.pendingEditorInputCloseTimeout = null; - this.stacksModel = this.instantiationService.createInstance(EditorStacksModel); + this.stacks = this.instantiationService.createInstance(EditorStacksModel); this.registerListeners(); } private registerListeners(): void { this.toUnbind.push(this.eventService.addListener(WorkbenchEventType.EDITOR_INPUT_STATE_CHANGED, (event: EditorInputEvent) => this.onEditorInputStateChanged(event))); + + const unbind = this.stacks.onEditorDisposed(editor => this.onEditorDisposed(editor)); + this.toUnbind.push(() => unbind.dispose()); } private onEditorInputStateChanged(event: EditorInputEvent): void { @@ -131,6 +132,11 @@ export class EditorPart extends Part implements IEditorPart { } } + private onEditorDisposed(identifier: IEditorIdentifier): void { + this.pendingEditorInputsToClose.push(identifier); + this.startDelayedCloseEditorsFromInputDispose(); + } + public openEditor(input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; public openEditor(input: EditorInput, options?: EditorOptions, position?: Position, widthRatios?: number[]): TPromise; public openEditor(input: EditorInput, options?: EditorOptions, arg3?: any, widthRatios?: number[]): TPromise { @@ -162,12 +168,6 @@ export class EditorPart extends Part implements IEditorPart { // Remember as visible input for this position this.visibleInputs[position] = input; - // Dispose previous input listener if any - if (this.visibleInputListeners[position]) { - this.visibleInputListeners[position](); - this.visibleInputListeners[position] = null; - } - // Open: input is provided return this.doOpenEditor(input, options, position, widthRatios); } @@ -190,18 +190,6 @@ export class EditorPart extends Part implements IEditorPart { options = this.findSideOptions(input, options, position); } - // Close editor when input provided and input gets disposed - this.visibleInputListeners[position] = input.addListener(EventType.DISPOSE, () => { - - // Keep the inputs to close. We use this to support multiple inputs closing - // right after each other and this helps avoid layout issues with the delayed - // timeout based closing below - if (input === this.visibleInputs[position]) { - this.pendingEditorInputsToClose.push({ input, position }); - this.startDelayedCloseEditorsFromInputDispose(); - } - }); - // Progress Monitor & Ref Counting this.editorOpenToken[position]++; const editorOpenToken = this.editorOpenToken[position]; @@ -523,12 +511,6 @@ export class EditorPart extends Part implements IEditorPart { // Update visible inputs for position this.visibleInputs[position] = null; - // Dispose previous input listener if any - if (this.visibleInputListeners[position]) { - this.visibleInputListeners[position](); - this.visibleInputListeners[position] = null; - } - // Reset counter this.editorSetInputErrorCounter[position] = 0; @@ -564,7 +546,7 @@ export class EditorPart extends Part implements IEditorPart { // Update stacks model const group = this.groupAt(position); - this.modifyGroups(() => this.stacksModel.closeGroup(group)); + this.modifyGroups(() => this.stacks.closeGroup(group)); // Emit Input-Changed Event this.emit(WorkbenchEventType.EDITOR_INPUT_CHANGED, new EditorEvent(null, null, null, null, position)); @@ -712,7 +694,7 @@ export class EditorPart extends Part implements IEditorPart { } public getStacksModel(): EditorStacksModel { - return this.stacksModel; + return this.stacks; } public getActiveEditorInput(): EditorInput { @@ -743,14 +725,13 @@ export class EditorPart extends Part implements IEditorPart { } // Update stacks model - this.modifyGroups(() => this.stacksModel.moveGroup(this.groupAt(from), to)); + this.modifyGroups(() => this.stacks.moveGroup(this.groupAt(from), to)); // Move widgets this.sideBySideControl.move(from, to); // Move data structures arrays.move(this.visibleInputs, from, to); - arrays.move(this.visibleInputListeners, from, to); arrays.move(this.visibleEditors, from, to); arrays.move(this.visibleEditorListeners, from, to); arrays.move(this.editorOpenToken, from, to); @@ -843,7 +824,7 @@ export class EditorPart extends Part implements IEditorPart { // Update stacks model let activePosition = this.sideBySideControl.getActivePosition(); if (typeof activePosition === 'number') { - this.stacksModel.setActive(this.groupAt(activePosition)); + this.stacks.setActive(this.groupAt(activePosition)); } // Emit as editor input change event so that clients get aware of new active editor @@ -873,7 +854,7 @@ export class EditorPart extends Part implements IEditorPart { } public restoreEditors(): TPromise { - const editors = this.stacksModel.groups.map((group, index) => { + const editors = this.stacks.groups.map((group, index) => { return { input: group.activeEditor, position: index @@ -888,8 +869,8 @@ export class EditorPart extends Part implements IEditorPart { let widthRatios = editorState.widthRatio; let activePosition: Position; - if (this.stacksModel.groups.length) { - activePosition = this.stacksModel.positionOfGroup(this.stacksModel.activeGroup); + if (this.stacks.groups.length) { + activePosition = this.stacks.positionOfGroup(this.stacks.activeGroup); } return this.doOpenEditors(editors, activePosition, widthRatios); @@ -960,7 +941,7 @@ export class EditorPart extends Part implements IEditorPart { } public groupAt(position: Position): EditorGroup { - return this.stacksModel.groups[position]; + return this.stacks.groups[position]; } public activateGroup(position: Position): void { @@ -968,7 +949,7 @@ export class EditorPart extends Part implements IEditorPart { if (editor) { // Update stacks model - this.stacksModel.setActive(this.groupAt(position)); + this.stacks.setActive(this.groupAt(position)); // Update UI this.sideBySideControl.setActive(editor); @@ -1105,16 +1086,6 @@ export class EditorPart extends Part implements IEditorPart { } } - // Input listeners - for (let i = 0; i < this.visibleInputListeners.length; i++) { - let listener = this.visibleInputListeners[i]; - if (listener) { - listener(); - } - - this.visibleInputListeners = []; - } - // Pass to active editors this.visibleEditors.forEach((editor) => { if (editor) { @@ -1245,11 +1216,27 @@ export class EditorPart extends Part implements IEditorPart { if (this.pendingEditorInputCloseTimeout === null) { this.pendingEditorInputCloseTimeout = setTimeout(() => { - // Close all - TPromise.join(this.pendingEditorInputsToClose - .sort((c1, c2) => c2.position - c1.position) // reduce layout work by starting right first - .map(c => this.closeEditor(c.position, c.input))) // close input at position - .done(null, errors.onUnexpectedError); + // Split between active and inactive editors + const activeEditors: IEditorIdentifier[] = []; + const inactiveEditors: IEditorIdentifier[] = []; + this.pendingEditorInputsToClose.forEach(identifier => { + if (identifier.group.isActive(identifier.editor)) { + activeEditors.push(identifier); + } else { + inactiveEditors.push(identifier); + } + }); + + // Close all inactive first + TPromise.join(inactiveEditors.map(inactive => this.closeEditor(this.stacks.positionOfGroup(inactive.group), inactive.editor))).done(() => { + + // Close active ones second + TPromise.join(activeEditors + .sort((a1, a2) => this.stacks.positionOfGroup(a2.group) - this.stacks.positionOfGroup(a1.group)) // reduce layout work by starting right first + .map(active => this.closeEditor(this.stacks.positionOfGroup(active.group), active.editor)) + ).done(null, errors.onUnexpectedError); + + }, errors.onUnexpectedError); // Reset this.pendingEditorInputCloseTimeout = null; @@ -1279,7 +1266,6 @@ export class EditorPart extends Part implements IEditorPart { let to = arg2; this.doRochade(this.visibleInputs, from, to, null); - this.doRochade(this.visibleInputListeners, from, to, null); this.doRochade(this.visibleEditors, from, to, null); this.doRochade(this.editorOpenToken, from, to, null); this.doRochade(this.mapEditorInstantiationPromiseToEditor, from, to, Object.create(null)); @@ -1306,18 +1292,18 @@ export class EditorPart extends Part implements IEditorPart { this.modifyGroups(() => { for (let i = 0; i < position; i++) { if (!this.hasGroup(i)) { - this.stacksModel.openGroup('', false, i); + this.stacks.openGroup('', false, i); } } - group = this.stacksModel.openGroup('', activate, position); + group = this.stacks.openGroup('', activate, position); }); } else { this.renameGroups(); // ensure group labels are proper } if (activate) { - this.stacksModel.setActive(group); + this.stacks.setActive(group); } return group; @@ -1333,25 +1319,25 @@ export class EditorPart extends Part implements IEditorPart { } private renameGroups(): void { - const groups = this.stacksModel.groups; + const groups = this.stacks.groups; if (groups.length > 0) { // LEFT | CENTER | RIGHT if (groups.length > 2) { - this.stacksModel.renameGroup(this.groupAt(Position.LEFT), EditorPart.GROUP_LEFT_LABEL); - this.stacksModel.renameGroup(this.groupAt(Position.CENTER), EditorPart.GROUP_CENTER_LABEL); - this.stacksModel.renameGroup(this.groupAt(Position.RIGHT), EditorPart.GROUP_RIGHT_LABEL); + this.stacks.renameGroup(this.groupAt(Position.LEFT), EditorPart.GROUP_LEFT_LABEL); + this.stacks.renameGroup(this.groupAt(Position.CENTER), EditorPart.GROUP_CENTER_LABEL); + this.stacks.renameGroup(this.groupAt(Position.RIGHT), EditorPart.GROUP_RIGHT_LABEL); } // LEFT | RIGHT else if (groups.length > 1) { - this.stacksModel.renameGroup(this.groupAt(Position.LEFT), EditorPart.GROUP_LEFT_LABEL); - this.stacksModel.renameGroup(this.groupAt(Position.CENTER), EditorPart.GROUP_RIGHT_LABEL); + this.stacks.renameGroup(this.groupAt(Position.LEFT), EditorPart.GROUP_LEFT_LABEL); + this.stacks.renameGroup(this.groupAt(Position.CENTER), EditorPart.GROUP_RIGHT_LABEL); } // LEFT else { - this.stacksModel.renameGroup(this.groupAt(Position.LEFT), EditorPart.GROUP_LEFT_LABEL); + this.stacks.renameGroup(this.groupAt(Position.LEFT), EditorPart.GROUP_LEFT_LABEL); } } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 37c7f31b9a8..61c13259d8e 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -53,6 +53,12 @@ export enum ConfirmResult { export abstract class EditorInput extends EventEmitter implements IEditorInput { private disposed: boolean; + constructor() { + super(); + + this.disposed = false; + } + /** * Returns the unique id of this input. */ @@ -96,13 +102,6 @@ export abstract class EditorInput extends EventEmitter implements IEditorInput { return null; } - /** - * Returns true if this input is identical to the otherInput. - */ - public matches(otherInput: any): boolean { - return this === otherInput; - } - /** * Returns a type of EditorModel that represents the resolved input. Subclasses should * override to provide a meaningful model. The optional second argument allows to specify @@ -140,9 +139,17 @@ export abstract class EditorInput extends EventEmitter implements IEditorInput { } /** - * Called when the editor is closed. Subclasses can free resources as needed. + * Called when this input is no longer opened in any editor. Subclasses can free resources as needed. */ public close(): void { + this.dispose(); + } + + /** + * Returns true if this input is identical to the otherInput. + */ + public matches(otherInput: any): boolean { + return this === otherInput; } /** @@ -280,11 +287,6 @@ export abstract class BaseDiffEditorInput extends EditorInput { public revert(): TPromise { return this._modifiedInput.revert(); } - - public close(): void { - this._originalInput.close(); - this._modifiedInput.close(); - } } /** diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 69e236f12c9..07d36b5bbd6 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -197,10 +197,6 @@ export class DiffEditorInput extends BaseDiffEditorInput { this.cachedModel = null; } - // Delegate to Inputs - this.originalInput.dispose(); - this.modifiedInput.dispose(); - super.dispose(); } } diff --git a/src/vs/workbench/common/editor/editorStacksModel.ts b/src/vs/workbench/common/editor/editorStacksModel.ts index 20807f22d22..ca53c6c7c8e 100644 --- a/src/vs/workbench/common/editor/editorStacksModel.ts +++ b/src/vs/workbench/common/editor/editorStacksModel.ts @@ -14,7 +14,8 @@ import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/c import {dispose, IDisposable} from 'vs/base/common/lifecycle'; import {IEditorRegistry, Extensions} from 'vs/workbench/browser/parts/editor/baseEditor'; import {Registry} from 'vs/platform/platform'; -import {Position, Direction, IEditorInput} from 'vs/platform/editor/common/editor'; +import {Position, Direction} from 'vs/platform/editor/common/editor'; +import {DiffEditorInput} from 'vs/workbench/common/editor/diffEditorInput'; export interface IEditorGroup { @@ -72,7 +73,7 @@ export interface IEditorStacksModel { export interface IEditorIdentifier { group: IEditorGroup; - editor: IEditorInput; + editor: EditorInput; } export type GroupIdentifier = number; @@ -118,6 +119,7 @@ export class EditorGroup implements IEditorGroup { private _onEditorActivated: Emitter; private _onEditorOpened: Emitter; private _onEditorClosed: Emitter; + private _onEditorDisposed: Emitter; private _onEditorMoved: Emitter; private _onEditorPinned: Emitter; private _onEditorUnpinned: Emitter; @@ -142,12 +144,13 @@ export class EditorGroup implements IEditorGroup { this._onEditorActivated = new Emitter(); this._onEditorOpened = new Emitter(); this._onEditorClosed = new Emitter(); + this._onEditorDisposed = new Emitter(); this._onEditorMoved = new Emitter(); this._onEditorPinned = new Emitter(); this._onEditorUnpinned = new Emitter(); this._onEditorChanged = new Emitter(); - this.toDispose.push(this._onEditorActivated, this._onEditorOpened, this._onEditorClosed, this._onEditorMoved, this._onEditorPinned, this._onEditorUnpinned, this._onEditorChanged); + this.toDispose.push(this._onEditorActivated, this._onEditorOpened, this._onEditorClosed, this._onEditorDisposed, this._onEditorMoved, this._onEditorPinned, this._onEditorUnpinned, this._onEditorChanged); } public get id(): GroupIdentifier { @@ -178,6 +181,10 @@ export class EditorGroup implements IEditorGroup { return this._onEditorClosed.event; } + public get onEditorDisposed(): Event { + return this._onEditorDisposed.event; + } + public get onEditorMoved(): Event { return this._onEditorMoved.event; } @@ -270,6 +277,21 @@ export class EditorGroup implements IEditorGroup { this.preview = editor; } + // Re-emit disposal of editor input as our own event + const l1 = editor.addOneTimeDisposableListener('dispose', () => { + if (this.indexOf(editor) >= 0) { + this._onEditorDisposed.fire(editor); + } + }); + + // Clean up dispose listeners once the editor gets closed + const l2 = this.onEditorClosed(event => { + if (event.editor.matches(editor)) { + l1.dispose(); + l2.dispose(); + } + }); + // Event this.fireEvent(this._onEditorOpened, editor); @@ -326,9 +348,6 @@ export class EditorGroup implements IEditorGroup { pinned = false; } - // Close it - editor.close(); - // Remove from arrays this.splice(index, true); @@ -587,6 +606,7 @@ export class EditorStacksModel implements IEditorStacksModel { private _onGroupActivated: Emitter; private _onGroupRenamed: Emitter; private _onModelChanged: Emitter; + private _onEditorDisposed: Emitter; constructor( @IStorageService private storageService: IStorageService, @@ -607,8 +627,9 @@ export class EditorStacksModel implements IEditorStacksModel { this._onGroupMoved = new Emitter(); this._onGroupRenamed = new Emitter(); this._onModelChanged = new Emitter(); + this._onEditorDisposed = new Emitter(); - this.toDispose.push(this._onGroupOpened, this._onGroupClosed, this._onGroupActivated, this._onGroupMoved, this._onGroupRenamed, this._onModelChanged); + this.toDispose.push(this._onGroupOpened, this._onGroupClosed, this._onGroupActivated, this._onGroupMoved, this._onGroupRenamed, this._onModelChanged, this._onEditorDisposed); this.registerListeners(); } @@ -641,6 +662,10 @@ export class EditorStacksModel implements IEditorStacksModel { return this._onModelChanged.event; } + public get onEditorDisposed(): Event { + return this._onEditorDisposed.event; + } + public get groups(): EditorGroup[] { this.ensureLoaded(); @@ -786,6 +811,8 @@ export class EditorStacksModel implements IEditorStacksModel { return this._groups.indexOf(group); } + public positionOfGroup(group: IEditorGroup); + public positionOfGroup(group: EditorGroup); public positionOfGroup(group: EditorGroup): Position { return this.indexOf(group); } @@ -988,9 +1015,10 @@ export class EditorStacksModel implements IEditorStacksModel { // Funnel editor changes in the group through our event aggregator const l1 = group.onEditorChanged(e => this._onModelChanged.fire(group)); const l2 = group.onEditorClosed(e => this.onEditorClosed(e)); - const l3 = this.onGroupClosed(g => { + const l3 = group.onEditorDisposed(editor => this._onEditorDisposed.fire({ editor, group })); + const l4 = this.onGroupClosed(g => { if (g === group) { - dispose(l1, l2, l3); + dispose(l1, l2, l3, l4); } }); @@ -1009,21 +1037,39 @@ export class EditorStacksModel implements IEditorStacksModel { } private onEditorClosed(event: IGroupEvent): void { - if (!event.pinned) { - return; // we only care about pinned editors - } - const editor = event.editor; - const registry = Registry.as(Extensions.Editors); - const factory = registry.getEditorInputFactory(editor.getId()); - if (factory) { - let value = factory.serialize(editor); - if (typeof value === 'string') { - this.recentlyClosedEditors.push({ id: editor.getId(), value }); - this.recentlyClosedEditors = this.recentlyClosedEditors.slice(0, 10); // upper bound of recently closed + // Close the editor when it is no longer open in any group + if (!this.isOpen(editor)) { + editor.close(); + + // Also take care of diff editor inputs that wrap around 2 editors + if (editor instanceof DiffEditorInput) { + [editor.getOriginalInput(), editor.getModifiedInput()].forEach(editor => { + if (!this.isOpen(editor)) { + editor.close(); + } + }); } } + + // Track closing of pinned editor to support to reopen closed editors + if (event.pinned) { + const registry = Registry.as(Extensions.Editors); + + const factory = registry.getEditorInputFactory(editor.getId()); + if (factory) { + let value = factory.serialize(editor); + if (typeof value === 'string') { + this.recentlyClosedEditors.push({ id: editor.getId(), value }); + this.recentlyClosedEditors = this.recentlyClosedEditors.slice(0, 10); // upper bound of recently closed + } + } + } + } + + private isOpen(editor: EditorInput): boolean { + return this._groups.some(g => g.indexOf(editor) >= 0); } private onShutdown(): void { diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorInput.ts index 1560921c318..46afa942eed 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorInput.ts @@ -275,6 +275,15 @@ export class FileEditorInput extends CommonFileEditorInput { }); } + public close(): void { + let inputs: FileEditorInput[] = [this, ...FileEditorInput.getAll(this.resource)]; + inputs.forEach(input => { + if (!input.isDisposed()) { + input.dispose(true); + } + }); + } + private indexOfClient(): number { if (!types.isUndefinedOrNull(FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()])) { for (let i = 0; i < FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()].length; i++) { diff --git a/src/vs/workbench/test/common/editor/editorInput.test.ts b/src/vs/workbench/test/common/editor/editorInput.test.ts index 5150134ab02..6a044afc58d 100644 --- a/src/vs/workbench/test/common/editor/editorInput.test.ts +++ b/src/vs/workbench/test/common/editor/editorInput.test.ts @@ -67,7 +67,7 @@ suite('Workbench - EditorInput', () => { assert(!diffInput.matches(null)); diffInput.dispose(); - assert.equal(counter, 2); + assert.equal(counter, 0); }); test('DiffEditorInput disposes when input inside disposes', function () { diff --git a/src/vs/workbench/test/common/editor/editorStacksModel.test.ts b/src/vs/workbench/test/common/editor/editorStacksModel.test.ts index a9d07a17c47..3cff30d954d 100644 --- a/src/vs/workbench/test/common/editor/editorStacksModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorStacksModel.test.ts @@ -6,7 +6,7 @@ 'use strict'; import * as assert from 'assert'; -import {EditorStacksModel, IEditorStacksModel, IEditorGroup, EditorGroup, setOpenEditorDirection} from 'vs/workbench/common/editor/editorStacksModel'; +import {EditorStacksModel, IEditorIdentifier, IEditorGroup, EditorGroup, setOpenEditorDirection} from 'vs/workbench/common/editor/editorStacksModel'; import {EditorInput} from 'vs/workbench/common/editor'; import {TestStorageService, TestLifecycleService, TestContextService, TestWorkspace, TestConfiguration} from 'vs/workbench/test/common/servicesTestUtils'; import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; @@ -18,6 +18,7 @@ import {IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory} fr import {Registry} from 'vs/platform/platform'; import {Position, Direction} from 'vs/platform/editor/common/editor'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {DiffEditorInput} from 'vs/workbench/common/editor/diffEditorInput'; function create(): EditorStacksModel { let services = new ServiceCollection(); @@ -36,6 +37,7 @@ interface ModelEvents { closed: IEditorGroup[]; moved: IEditorGroup[]; renamed: IEditorGroup[]; + disposed: IEditorIdentifier[]; } interface GroupEvents { @@ -47,13 +49,14 @@ interface GroupEvents { moved: EditorInput[]; } -function modelListener(model: IEditorStacksModel): ModelEvents { +function modelListener(model: EditorStacksModel): ModelEvents { const modelEvents = { opened: [], activated: [], closed: [], moved: [], - renamed: [] + renamed: [], + disposed: [] }; model.onGroupOpened(g => modelEvents.opened.push(g)); @@ -61,6 +64,7 @@ function modelListener(model: IEditorStacksModel): ModelEvents { model.onGroupClosed(g => modelEvents.closed.push(g)); model.onGroupMoved(g => modelEvents.moved.push(g)); model.onGroupRenamed(g => modelEvents.renamed.push(g)); + model.onEditorDisposed(e => modelEvents.disposed.push(e)); return modelEvents; } @@ -1395,4 +1399,108 @@ suite('Editor Stacks Model', () => { assert.equal(previous.group, group1); assert.equal(previous.editor, input3); }); + + test('Stack - Multiple Editors - Editor Dispose', function () { + const model = create(); + const events = modelListener(model); + + const group1 = model.openGroup('group1'); + const group2 = model.openGroup('group2'); + + const input1 = input(); + const input2 = input(); + const input3 = input(); + + group1.openEditor(input1, { pinned: true, active: true }); + group1.openEditor(input2, { pinned: true, active: true }); + group1.openEditor(input3, { pinned: true, active: true }); + + group2.openEditor(input1, { pinned: true, active: true }); + group2.openEditor(input2, { pinned: true, active: true }); + + input1.dispose(); + + assert.equal(events.disposed.length, 2); + assert.ok(events.disposed[0].editor.matches(input1)); + assert.ok(events.disposed[1].editor.matches(input1)); + + input3.dispose(); + assert.equal(events.disposed.length, 3); + assert.ok(events.disposed[2].editor.matches(input3)); + + const input4 = input(); + const input5 = input(); + + group1.openEditor(input4, { pinned: false, active: true }); + group1.openEditor(input5, { pinned: false, active: true }); + + input4.dispose(); + assert.equal(events.disposed.length, 3); + + model.closeGroup(group2); + + input2.dispose(); + assert.equal(events.disposed.length, 4); + }); + + test('Stack - Multiple Editors - Editor Disposed on Close', function () { + const model = create(); + + const group1 = model.openGroup('group1'); + const group2 = model.openGroup('group2'); + + const input1 = input(); + const input2 = input(); + const input3 = input(); + const input4 = input(); + + group1.openEditor(input1, { pinned: true, active: true }); + group1.openEditor(input2, { pinned: true, active: true }); + group1.openEditor(input3, { pinned: true, active: true }); + group1.openEditor(input4, { pinned: true, active: true }); + + group1.closeEditor(input3); + + assert.equal(input3.isDisposed(), true); + + group2.openEditor(input2, { pinned: true, active: true }); + group2.openEditor(input3, { pinned: true, active: true }); + group2.openEditor(input4, { pinned: true, active: true }); + + group1.closeEditor(input2); + + assert.equal(input2.isDisposed(), false); + + group2.closeEditor(input2); + + assert.equal(input2.isDisposed(), true); + + group1.closeAllEditors(); + + assert.equal(input4.isDisposed(), false); + + model.closeGroups(); + + assert.equal(input4.isDisposed(), true); + }); + + test('Stack - Multiple Editors - Editor Disposed on Close (Diff Editor)', 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(diffInput, { pinned: true, active: true}); + group1.openEditor(input1, { pinned: true, active: true }); + + group1.closeEditor(diffInput); + + assert.equal(diffInput.isDisposed(), true); + assert.equal(input2.isDisposed(), true); + assert.equal(input1.isDisposed(), false); + }); }); \ No newline at end of file