Skip to content

Commit cff4fe2

Browse files
authored
Merge pull request microsoft#185798 from microsoft/aamunger/saveAllScratchpad
include scratchpads in saveAll action on shutdown
2 parents 670a8e5 + c894761 commit cff4fe2

File tree

11 files changed

+114
-24
lines changed

11 files changed

+114
-24
lines changed

src/vs/workbench/common/editor/editorInput.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ export abstract class EditorInput extends AbstractEditorInput {
185185
return false;
186186
}
187187

188+
/**
189+
* Returns if the input has unsaved changes.
190+
*/
191+
isModified(): boolean {
192+
return this.isDirty();
193+
}
194+
188195
/**
189196
* Returns if this input is currently being saved or soon to be
190197
* saved. Based on this assumption the editor may for example

src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,11 @@ suite('Files - FileEditorInput', () => {
259259
const resolved = await input.resolve() as TextFileEditorModel;
260260
resolved.textEditorModel!.setValue('changed');
261261
assert.ok(input.isDirty());
262+
assert.ok(input.isModified());
262263

263264
await input.save(0);
264265
assert.ok(!input.isDirty());
266+
assert.ok(!input.isModified());
265267
resolved.dispose();
266268
});
267269

@@ -271,9 +273,11 @@ suite('Files - FileEditorInput', () => {
271273
const resolved = await input.resolve() as TextFileEditorModel;
272274
resolved.textEditorModel!.setValue('changed');
273275
assert.ok(input.isDirty());
276+
assert.ok(input.isModified());
274277

275278
await input.revert(0);
276279
assert.ok(!input.isDirty());
280+
assert.ok(!input.isModified());
277281

278282
input.dispose();
279283
assert.ok(input.isDisposed());

src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
219219
return basename.substr(0, basename.length - paths.extname(p).length);
220220
}
221221

222+
override isModified() {
223+
return this._editorModelReference?.isModified() ?? false;
224+
}
225+
222226
override dispose() {
223227
// we support closing the interactive window without prompt, so the editor model should not be dirty
224228
this._editorModelReference?.revert({ soft: true });

src/vs/workbench/contrib/notebook/common/notebookCommon.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,7 @@ export interface INotebookEditorModel extends IEditorModel {
798798
readonly notebook: INotebookTextModel | undefined;
799799
isResolved(): this is IResolvedNotebookEditorModel;
800800
isDirty(): boolean;
801+
isModified(): boolean;
801802
isReadonly(): boolean | IMarkdownString;
802803
isOrphaned(): boolean;
803804
hasAssociatedFilePath(): boolean;

src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
8585
return this._workingCopy?.isDirty() ?? false;
8686
}
8787

88+
isModified(): boolean {
89+
return this._workingCopy?.isModified() ?? false;
90+
}
91+
8892
isOrphaned(): boolean {
8993
return SimpleNotebookEditorModel._isStoredFileWorkingCopy(this._workingCopy) && this._workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN);
9094
}

src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi
135135
return this._dirty;
136136
}
137137

138+
isModified(): boolean {
139+
return this._dirty;
140+
}
141+
138142
getNotebook(): NotebookTextModel {
139143
return this._notebook;
140144
}

src/vs/workbench/services/editor/browser/editorService.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
980980
}
981981

982982
saveAll(options?: ISaveAllEditorsOptions): Promise<ISaveEditorsResult> {
983-
return this.save(this.getAllDirtyEditors(options), options);
983+
return this.save(this.getAllModifiedEditors(options), options);
984984
}
985985

986986
async revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise<boolean> {
@@ -1006,15 +1006,20 @@ export class EditorService extends Disposable implements EditorServiceImpl {
10061006
}
10071007

10081008
async revertAll(options?: IRevertAllEditorsOptions): Promise<boolean> {
1009-
return this.revert(this.getAllDirtyEditors(options), options);
1009+
return this.revert(this.getAllModifiedEditors(options), options);
10101010
}
10111011

1012-
private getAllDirtyEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] {
1012+
private getAllModifiedEditors(options?: IBaseSaveRevertAllEditorOptions): IEditorIdentifier[] {
10131013
const editors: IEditorIdentifier[] = [];
10141014

10151015
for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
10161016
for (const editor of group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) {
1017-
if (!editor.isDirty()) {
1017+
if (!editor.isModified()) {
1018+
continue;
1019+
}
1020+
1021+
if ((typeof options?.includeUntitled === 'boolean' || !options?.includeUntitled?.includeScratchpad)
1022+
&& editor.hasCapability(EditorInputCapabilities.Scratchpad)) {
10181023
continue;
10191024
}
10201025

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,15 @@ export interface IBaseSaveRevertAllEditorOptions {
8080
/**
8181
* Whether to include untitled editors as well.
8282
*/
83-
readonly includeUntitled?: boolean;
83+
readonly includeUntitled?: {
84+
85+
/**
86+
* Whether to include scratchpad editors.
87+
* Scratchpads are not included if not specified.
88+
*/
89+
readonly includeScratchpad: boolean;
90+
91+
} | boolean;
8492

8593
/**
8694
* Whether to exclude sticky editors.

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import * as assert from 'assert';
77
import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor';
88
import { URI } from 'vs/base/common/uri';
99
import { Event } from 'vs/base/common/event';
10-
import { DEFAULT_EDITOR_ASSOCIATION, EditorCloseContext, EditorsOrder, IEditorCloseEvent, EditorInputWithOptions, IEditorPane, IResourceDiffEditorInput, isEditorInputWithOptions, IUntitledTextResourceEditorInput, IUntypedEditorInput, SideBySideEditor, isEditorInput } from 'vs/workbench/common/editor';
10+
import { DEFAULT_EDITOR_ASSOCIATION, EditorCloseContext, EditorsOrder, IEditorCloseEvent, EditorInputWithOptions, IEditorPane, IResourceDiffEditorInput, isEditorInputWithOptions, IUntitledTextResourceEditorInput, IUntypedEditorInput, SideBySideEditor, isEditorInput, EditorInputCapabilities } from 'vs/workbench/common/editor';
1111
import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart, registerTestFileEditor, TestTextFileEditor, TestSingletonFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices';
1212
import { EditorService } from 'vs/workbench/services/editor/browser/editorService';
1313
import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService';
1414
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
15-
import { ACTIVE_GROUP, IEditorService, PreferredGroup, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
15+
import { ACTIVE_GROUP, IBaseSaveRevertAllEditorOptions, IEditorService, PreferredGroup, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
1616
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
1717
import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput';
1818
import { timeout } from 'vs/base/common/async';
@@ -2173,6 +2173,61 @@ suite('EditorService', () => {
21732173
assert.strictEqual(sameInput1.gotSaved, true);
21742174
});
21752175

2176+
test('saveAll, revertAll untitled (exclude untitled)', async function () {
2177+
await testSaveRevertUntitled({}, false, false);
2178+
await testSaveRevertUntitled({ includeUntitled: false }, false, false);
2179+
});
2180+
2181+
test('saveAll, revertAll untitled (include untitled)', async function () {
2182+
await testSaveRevertUntitled({ includeUntitled: true }, true, false);
2183+
await testSaveRevertUntitled({ includeUntitled: { includeScratchpad: false } }, true, false);
2184+
});
2185+
2186+
test('saveAll, revertAll untitled (include scratchpad)', async function () {
2187+
await testSaveRevertUntitled({ includeUntitled: { includeScratchpad: true } }, true, true);
2188+
});
2189+
2190+
async function testSaveRevertUntitled(options: IBaseSaveRevertAllEditorOptions, expectUntitled: boolean, expectScratchpad: boolean) {
2191+
const [, service] = await createEditorService();
2192+
const input1 = new TestFileEditorInput(URI.parse('my://resource1'), TEST_EDITOR_INPUT_ID);
2193+
input1.dirty = true;
2194+
const untitledInput = new TestFileEditorInput(URI.parse('my://resource2'), TEST_EDITOR_INPUT_ID);
2195+
untitledInput.dirty = true;
2196+
untitledInput.capabilities = EditorInputCapabilities.Untitled;
2197+
const scratchpadInput = new TestFileEditorInput(URI.parse('my://resource3'), TEST_EDITOR_INPUT_ID);
2198+
scratchpadInput.modified = true;
2199+
scratchpadInput.capabilities = EditorInputCapabilities.Scratchpad | EditorInputCapabilities.Untitled;
2200+
2201+
await service.openEditor(input1, { pinned: true, sticky: true });
2202+
await service.openEditor(untitledInput, { pinned: true });
2203+
await service.openEditor(scratchpadInput, { pinned: true });
2204+
2205+
const revertRes = await service.revertAll(options);
2206+
assert.strictEqual(revertRes, true);
2207+
assert.strictEqual(input1.gotReverted, true);
2208+
assert.strictEqual(untitledInput.gotReverted, expectUntitled);
2209+
assert.strictEqual(scratchpadInput.gotReverted, expectScratchpad);
2210+
2211+
input1.gotSaved = false;
2212+
untitledInput.gotSavedAs = false;
2213+
scratchpadInput.gotReverted = false;
2214+
2215+
input1.gotSaved = false;
2216+
untitledInput.gotSavedAs = false;
2217+
scratchpadInput.gotReverted = false;
2218+
2219+
input1.dirty = true;
2220+
untitledInput.dirty = true;
2221+
scratchpadInput.modified = true;
2222+
2223+
const saveRes = await service.saveAll(options);
2224+
assert.strictEqual(saveRes.success, true);
2225+
assert.strictEqual(saveRes.editors.length, expectScratchpad ? 3 : expectUntitled ? 2 : 1);
2226+
assert.strictEqual(input1.gotSaved, true);
2227+
assert.strictEqual(untitledInput.gotSaved, expectUntitled);
2228+
assert.strictEqual(scratchpadInput.gotSaved, expectScratchpad);
2229+
}
2230+
21762231
test('file delete closes editor', async function () {
21772232
return testFileDeleteEditorClose(false);
21782233
});

src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
8989

9090
if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) {
9191

92-
// Save all modified working copies
92+
// Save all modified working copies that can be auto-saved
9393
try {
94-
await this.doSaveAllBeforeShutdown(false /* not untitled */, SaveReason.AUTO);
94+
const workingCopiesToSave = modifiedWorkingCopies.filter(wc => !(wc.capabilities & WorkingCopyCapabilities.Untitled));
95+
await this.doSaveAllBeforeShutdown(workingCopiesToSave, SaveReason.AUTO);
9596
} catch (error) {
9697
this.logService.error(`[backup tracker] error saving modified working copies: ${error}`); // guard against misbehaving saves, we handle remaining modified below
9798
}
@@ -302,34 +303,26 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
302303
return true; // veto (user canceled)
303304
}
304305

305-
private doSaveAllBeforeShutdown(modifiedWorkingCopies: IWorkingCopy[], reason: SaveReason): Promise<void>;
306-
private doSaveAllBeforeShutdown(includeUntitled: boolean, reason: SaveReason): Promise<void>;
307-
private doSaveAllBeforeShutdown(arg1: IWorkingCopy[] | boolean, reason: SaveReason): Promise<void> {
308-
const modifiedWorkingCopies = Array.isArray(arg1) ? arg1 : this.workingCopyService.modifiedWorkingCopies.filter(workingCopy => {
309-
if (arg1 === false && (workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) {
310-
return false; // skip untitled unless explicitly included
311-
}
312-
313-
return true;
314-
});
315-
306+
private doSaveAllBeforeShutdown(workingCopies: IWorkingCopy[], reason: SaveReason): Promise<void> {
316307
return this.withProgressAndCancellation(async () => {
317308

318309
// Skip save participants on shutdown for performance reasons
319310
const saveOptions = { skipSaveParticipants: true, reason };
320311

321312
// First save through the editor service if we save all to benefit
322313
// from some extras like switching to untitled modified editors before saving.
323-
324314
let result: boolean | undefined = undefined;
325-
if (typeof arg1 === 'boolean' || modifiedWorkingCopies.length === this.workingCopyService.modifiedCount) {
326-
result = (await this.editorService.saveAll({ includeUntitled: typeof arg1 === 'boolean' ? arg1 : true, ...saveOptions })).success;
315+
if (workingCopies.length === this.workingCopyService.modifiedCount) {
316+
result = (await this.editorService.saveAll({
317+
includeUntitled: { includeScratchpad: true },
318+
...saveOptions
319+
})).success;
327320
}
328321

329322
// If we still have modified working copies, save those directly
330323
// unless the save was not successful (e.g. cancelled)
331324
if (result !== false) {
332-
await Promises.settled(modifiedWorkingCopies.map(workingCopy => workingCopy.isModified() ? workingCopy.save(saveOptions) : Promise.resolve(true)));
325+
await Promises.settled(workingCopies.map(workingCopy => workingCopy.isModified() ? workingCopy.save(saveOptions) : Promise.resolve(true)));
333326
}
334327
}, localize('saveBeforeShutdown', "Saving editors with unsaved changes is taking a bit longer..."));
335328
}

0 commit comments

Comments
 (0)