Skip to content

Commit f9158a4

Browse files
authored
editors - allow to apply empty working set (microsoft#210167)
1 parent 6b391eb commit f9158a4

File tree

6 files changed

+92
-35
lines changed

6 files changed

+92
-35
lines changed

src/vs/workbench/browser/parts/editor/editorPart.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,16 +1342,16 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
13421342
};
13431343
}
13441344

1345-
async applyState(state: IEditorPartUIState): Promise<void> {
1346-
1347-
// Before disposing groups, try to close as many editors as
1348-
// possible, but skip over those that would trigger a dialog
1349-
// (for example when being dirty). This is to be able to later
1350-
// restore these editors after state has been applied.
1351-
const groups = this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
1352-
for (const group of groups) {
1353-
await group.closeAllEditors({ excludeConfirming: true });
1345+
applyState(state: IEditorPartUIState | 'empty'): Promise<void> {
1346+
if (state === 'empty') {
1347+
return this.doApplyEmptyState();
1348+
} else {
1349+
return this.doApplyState(state);
13541350
}
1351+
}
1352+
1353+
private async doApplyState(state: IEditorPartUIState): Promise<void> {
1354+
const groups = await this.doPrepareApplyState();
13551355
const resumeEvents = this.disposeGroups(true /* suspress events for the duration of applying state */);
13561356

13571357
// MRU
@@ -1375,6 +1375,27 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView {
13751375
);
13761376
}
13771377

1378+
private async doApplyEmptyState(): Promise<void> {
1379+
await this.doPrepareApplyState();
1380+
1381+
this.mergeAllGroups(this.activeGroup);
1382+
}
1383+
1384+
private async doPrepareApplyState(): Promise<IEditorGroupView[]> {
1385+
1386+
// Before disposing groups, try to close as many editors as
1387+
// possible, but skip over those that would trigger a dialog
1388+
// (for example when being dirty). This is to be able to later
1389+
// restore these editors after state has been applied.
1390+
1391+
const groups = this.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
1392+
for (const group of groups) {
1393+
await group.closeAllEditors({ excludeConfirming: true });
1394+
}
1395+
1396+
return groups;
1397+
}
1398+
13781399
private doApplyGridState(gridState: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void {
13791400

13801401
// Recreate grid widget from state

src/vs/workbench/browser/parts/editor/editorParts.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,12 +296,13 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
296296
}
297297
}
298298

299-
private async applyState(state: IEditorPartsUIState): Promise<boolean> {
299+
private async applyState(state: IEditorPartsUIState | 'empty'): Promise<boolean> {
300300

301301
// Before closing windows, try to close as many editors as
302302
// possible, but skip over those that would trigger a dialog
303303
// (for example when being dirty). This is to be able to have
304304
// them merge into the main part.
305+
305306
for (const part of this.parts) {
306307
if (part === this.mainPart) {
307308
continue; // main part takes care on its own
@@ -317,8 +318,10 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
317318
}
318319
}
319320

320-
// Restore auxiliary state
321-
await this.restoreState(state);
321+
// Restore auxiliary state unless we are in an empty state
322+
if (state !== 'empty') {
323+
await this.restoreState(state);
324+
}
322325

323326
return true;
324327
}
@@ -369,8 +372,14 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
369372
}
370373
}
371374

372-
async applyWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> {
373-
const workingSetState = this.editorWorkingSets[this.indexOfWorkingSet(workingSet) ?? -1];
375+
async applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean> {
376+
let workingSetState: IEditorWorkingSetState | 'empty' | undefined;
377+
if (workingSet === 'empty') {
378+
workingSetState = 'empty';
379+
} else {
380+
workingSetState = this.editorWorkingSets[this.indexOfWorkingSet(workingSet) ?? -1];
381+
}
382+
374383
if (!workingSetState) {
375384
return false;
376385
}
@@ -379,11 +388,11 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
379388
// editors around that need confirmation by moving them into the main part.
380389
// Also, in rare cases, the auxiliary part may not be able to apply the state
381390
// for certain editors that cannot move to the main part.
382-
const applied = await this.applyState(workingSetState.auxiliary);
391+
const applied = await this.applyState(workingSetState === 'empty' ? workingSetState : workingSetState.auxiliary);
383392
if (!applied) {
384393
return false;
385394
}
386-
await this.mainPart.applyState(workingSetState.main);
395+
await this.mainPart.applyState(workingSetState === 'empty' ? workingSetState : workingSetState.main);
387396

388397
// Restore Focus
389398
const mostRecentActivePart = firstOrDefault(this.mostRecentActiveParts);

src/vs/workbench/contrib/scm/browser/workingSet.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,13 @@
55

66
import { Event } from 'vs/base/common/event';
77
import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle';
8-
import { Schemas } from 'vs/base/common/network';
98
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
109
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
1110
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
12-
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
1311
import { getProviderKey } from 'vs/workbench/contrib/scm/browser/util';
1412
import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
1513
import { IEditorGroupsService, IEditorWorkingSet } from 'vs/workbench/services/editor/common/editorGroupsService';
1614

17-
function workingSetEditorFilter(editor: EditorInput): boolean {
18-
return editor.resource?.scheme !== Schemas.untitled && !editor.isDirty();
19-
}
20-
2115
type ISCMSerializedWorkingSet = {
2216
readonly providerKey: string;
2317
readonly currentHistoryItemGroupId: string;
@@ -147,13 +141,13 @@ export class SCMWorkingSetController implements IWorkbenchContribution {
147141
return;
148142
}
149143

150-
const editorWorkingSetId = workingSets.editorWorkingSets.get(currentHistoryItemGroupId);
144+
let editorWorkingSetId: IEditorWorkingSet | 'empty' | undefined = workingSets.editorWorkingSets.get(currentHistoryItemGroupId);
145+
if (!editorWorkingSetId && this.configurationService.getValue<'empty' | 'current'>('scm.workingSets.default') === 'empty') {
146+
editorWorkingSetId = 'empty';
147+
}
148+
151149
if (editorWorkingSetId) {
152150
await this.editorGroupsService.applyWorkingSet(editorWorkingSetId);
153-
} else if (this.configurationService.getValue<'empty' | 'current'>('scm.workingSets.default') === 'empty') {
154-
await Promise.all(this.editorGroupsService.groups.map(group => {
155-
return group.closeEditors(group.editors.filter(workingSetEditorFilter));
156-
}));
157151
}
158152
}
159153

src/vs/workbench/services/editor/common/editorGroupsService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,11 +551,11 @@ export interface IEditorGroupsService extends IEditorGroupsContainer {
551551
getWorkingSets(): IEditorWorkingSet[];
552552

553553
/**
554-
* Applies the working set.
554+
* Applies the working set. Use `empty` to apply an empty working set.
555555
*
556556
* @returns `true` when the working set as applied.
557557
*/
558-
applyWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean>;
558+
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean>;
559559

560560
/**
561561
* Deletes a working set.

src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as assert from 'assert';
77
import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, TestEditorPart, TestServiceAccessor, createEditorPart, ITestInstantiationService, workbenchTeardown } from 'vs/workbench/test/browser/workbenchTestServices';
88
import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupLocation, isEditorGroup, IEditorGroupsService, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
9-
import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities, GroupModelChangeKind, SideBySideEditor } from 'vs/workbench/common/editor';
9+
import { CloseDirection, IEditorPartOptions, EditorsOrder, EditorInputCapabilities, GroupModelChangeKind, SideBySideEditor, IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
1010
import { URI } from 'vs/base/common/uri';
1111
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
1212
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -18,6 +18,7 @@ import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEdit
1818
import { IGroupModelChangeEvent, IGroupEditorMoveEvent, IGroupEditorOpenEvent } from 'vs/workbench/common/editor/editorGroupModel';
1919
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
2020
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
21+
import { Registry } from 'vs/platform/registry/common/platform';
2122

2223
suite('EditorGroupsService', () => {
2324

@@ -42,6 +43,7 @@ suite('EditorGroupsService', () => {
4243
});
4344

4445
async function createPart(instantiationService = workbenchInstantiationService(undefined, disposables)): Promise<[TestEditorPart, TestInstantiationService]> {
46+
instantiationService.invokeFunction(accessor => Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).start(accessor));
4547
const part = await createEditorPart(instantiationService, disposables);
4648
instantiationService.stub(IEditorGroupsService, part);
4749

@@ -1915,12 +1917,10 @@ suite('EditorGroupsService', () => {
19151917
test('working sets - create / apply state', async function () {
19161918
const [part] = await createPart();
19171919

1918-
const group = part.activeGroup;
1919-
19201920
const input = createTestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID);
19211921
const input2 = createTestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID);
19221922

1923-
const pane1 = await group.openEditor(input, { pinned: true });
1923+
const pane1 = await part.activeGroup.openEditor(input, { pinned: true });
19241924
const pane2 = await part.sideGroup.openEditor(input2, { pinned: true });
19251925

19261926
const state = part.createState();
@@ -1934,6 +1934,39 @@ suite('EditorGroupsService', () => {
19341934
await part.applyState(state);
19351935

19361936
assert.strictEqual(part.count, 2);
1937+
1938+
assert.strictEqual(part.groups[0].contains(input), true);
1939+
assert.strictEqual(part.groups[1].contains(input2), true);
1940+
1941+
for (const group of part.groups) {
1942+
await group.closeAllEditors();
1943+
}
1944+
1945+
const emptyState = part.createState();
1946+
1947+
await part.applyState(emptyState);
1948+
assert.strictEqual(part.count, 1);
1949+
1950+
const input3 = createTestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID);
1951+
input3.dirty = true;
1952+
await part.activeGroup.openEditor(input3, { pinned: true });
1953+
1954+
await part.applyState(emptyState);
1955+
1956+
assert.strictEqual(part.count, 1);
1957+
assert.strictEqual(part.groups[0].contains(input3), true); // dirty editors enforce to be there even when state is empty
1958+
1959+
await part.applyState('empty');
1960+
1961+
assert.strictEqual(part.count, 1);
1962+
assert.strictEqual(part.groups[0].contains(input3), true); // dirty editors enforce to be there even when state is empty
1963+
1964+
input3.dirty = false;
1965+
1966+
await part.applyState('empty');
1967+
1968+
assert.strictEqual(part.count, 1);
1969+
assert.strictEqual(part.activeGroup.isEmpty, true);
19371970
});
19381971

19391972
ensureNoDisposablesAreLeakedInTestSuite();

src/vs/workbench/test/browser/workbenchTestServices.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ export class TestEditorGroupsService implements IEditorGroupsService {
841841
getPart(group: number | IEditorGroup): IEditorPart { return this; }
842842
saveWorkingSet(name: string): IEditorWorkingSet { throw new Error('Method not implemented.'); }
843843
getWorkingSets(): IEditorWorkingSet[] { throw new Error('Method not implemented.'); }
844-
applyWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> { throw new Error('Method not implemented.'); }
844+
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean> { throw new Error('Method not implemented.'); }
845845
deleteWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> { throw new Error('Method not implemented.'); }
846846
getGroups(_order?: GroupsOrder): readonly IEditorGroup[] { return this.groups; }
847847
getGroup(identifier: number): IEditorGroup | undefined { return this.groups.find(group => group.id === identifier); }
@@ -1837,7 +1837,7 @@ export class TestEditorPart extends MainEditorPart implements IEditorGroupsServi
18371837

18381838
saveWorkingSet(name: string): IEditorWorkingSet { throw new Error('Method not implemented.'); }
18391839
getWorkingSets(): IEditorWorkingSet[] { throw new Error('Method not implemented.'); }
1840-
applyWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> { throw new Error('Method not implemented.'); }
1840+
applyWorkingSet(workingSet: IEditorWorkingSet | 'empty'): Promise<boolean> { throw new Error('Method not implemented.'); }
18411841
deleteWorkingSet(workingSet: IEditorWorkingSet): Promise<boolean> { throw new Error('Method not implemented.'); }
18421842
}
18431843

0 commit comments

Comments
 (0)