Skip to content

Commit c97098e

Browse files
authored
Merge pull request microsoft#181280 from microsoft/aamunger/scrapbookIW
create a scratchpad untitled workingcopy
2 parents aa79bd7 + 4864d33 commit c97098e

22 files changed

+588
-103
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,9 +982,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
982982
this._onWillOpenEditor.fire({ editor, groupId: this.id });
983983

984984
// Determine options
985+
const pinned = options?.sticky
986+
|| !this.accessor.partOptions.enablePreview
987+
|| editor.isDirty()
988+
|| (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */)
989+
|| (typeof options?.index === 'number' && this.model.isSticky(options.index))
990+
|| editor.hasCapability(EditorInputCapabilities.Scratchpad);
985991
const openEditorOptions: IEditorOpenOptions = {
986992
index: options ? options.index : undefined,
987-
pinned: options?.sticky || !this.accessor.partOptions.enablePreview || editor.isDirty() || (options?.pinned ?? typeof options?.index === 'number' /* unless specified, prefer to pin when opening with index */) || (typeof options?.index === 'number' && this.model.isSticky(options.index)),
993+
pinned,
988994
sticky: options?.sticky || (typeof options?.index === 'number' && this.model.isSticky(options.index)),
989995
active: this.count === 0 || !options || !options.inactive,
990996
supportSideBySide: internalOptions?.supportSideBySide

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IEditorFactoryRegistry, IEditorIdentifier, GroupIdentifier, EditorExtensions, IEditorPartOptionsChangeEvent, EditorsOrder, GroupModelChangeKind } from 'vs/workbench/common/editor';
6+
import { IEditorFactoryRegistry, IEditorIdentifier, GroupIdentifier, EditorExtensions, IEditorPartOptionsChangeEvent, EditorsOrder, GroupModelChangeKind, EditorInputCapabilities } from 'vs/workbench/common/editor';
77
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
88
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
99
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
@@ -345,8 +345,8 @@ export class EditorsObserver extends Disposable {
345345
let mostRecentEditorsCountingForLimit: IEditorIdentifier[];
346346
if (this.editorGroupsService.partOptions.limit?.excludeDirty) {
347347
mostRecentEditorsCountingForLimit = mostRecentEditors.filter(({ editor }) => {
348-
if (editor.isDirty() && !editor.isSaving()) {
349-
return false;
348+
if ((editor.isDirty() && !editor.isSaving()) || editor.hasCapability(EditorInputCapabilities.Scratchpad)) {
349+
return false; // not dirty editors (unless in the process of saving) or scratchpads
350350
}
351351

352352
return true;
@@ -361,8 +361,8 @@ export class EditorsObserver extends Disposable {
361361

362362
// Extract least recently used editors that can be closed
363363
const leastRecentlyClosableEditors = mostRecentEditorsCountingForLimit.reverse().filter(({ editor, groupId }) => {
364-
if (editor.isDirty() && !editor.isSaving()) {
365-
return false; // not dirty editors (unless in the process of saving)
364+
if ((editor.isDirty() && !editor.isSaving()) || editor.hasCapability(EditorInputCapabilities.Scratchpad)) {
365+
return false; // not dirty editors (unless in the process of saving) or scratchpads
366366
}
367367

368368
if (exclude && editor === exclude.editor && groupId === exclude.groupId) {

src/vs/workbench/common/editor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,13 @@ export const enum EditorInputCapabilities {
750750
* Signals that the editor is composed of multiple editors
751751
* within.
752752
*/
753-
MultipleEditors = 1 << 8
753+
MultipleEditors = 1 << 8,
754+
755+
/**
756+
* Signals that the editor cannot be in a dirty state
757+
* and may still have unsaved changes
758+
*/
759+
Scratchpad = 1 << 9
754760
}
755761

756762
export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput;

src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export class SearchEditorInput extends EditorInput {
131131
readonly onDidChangeContent = input.onDidChangeContent;
132132
readonly onDidSave = input.onDidSave;
133133
isDirty(): boolean { return input.isDirty(); }
134+
isModified(): boolean { return input.isDirty(); }
134135
backup(token: CancellationToken): Promise<IWorkingCopyBackup> { return input.backup(token); }
135136
save(options?: ISaveOptions): Promise<boolean> { return input.save(0, options).then(editor => !!editor); }
136137
revert(options?: IRevertOptions): Promise<void> { return input.revert(0, options); }

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as assert from 'assert';
7-
import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor';
7+
import { IEditorFactoryRegistry, EditorExtensions, EditorInputCapabilities } from 'vs/workbench/common/editor';
88
import { URI } from 'vs/base/common/uri';
99
import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor, TestEditorPart, createEditorPart, registerTestSideBySideEditor } from 'vs/workbench/test/browser/workbenchTestServices';
1010
import { Registry } from 'vs/platform/registry/common/platform';
@@ -628,4 +628,35 @@ suite('EditorsObserver', function () {
628628
assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId, editorId: input3.editorId }), true);
629629
assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId, editorId: input4.editorId }), true);
630630
});
631+
632+
test('observer does not close scratchpads', async () => {
633+
const [part] = await createPart();
634+
part.enforcePartOptions({ limit: { enabled: true, value: 3 } });
635+
636+
const storage = new TestStorageService();
637+
const observer = disposables.add(new EditorsObserver(part, storage));
638+
639+
const rootGroup = part.activeGroup;
640+
641+
const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID);
642+
input1.capabilities = EditorInputCapabilities.Untitled | EditorInputCapabilities.Scratchpad;
643+
const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID);
644+
const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID);
645+
const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID);
646+
647+
await rootGroup.openEditor(input1, { pinned: true });
648+
await rootGroup.openEditor(input2, { pinned: true });
649+
await rootGroup.openEditor(input3, { pinned: true });
650+
await rootGroup.openEditor(input4, { pinned: true });
651+
652+
assert.strictEqual(rootGroup.count, 3);
653+
assert.strictEqual(rootGroup.contains(input1), true);
654+
assert.strictEqual(rootGroup.contains(input2), false);
655+
assert.strictEqual(rootGroup.contains(input3), true);
656+
assert.strictEqual(rootGroup.contains(input4), true);
657+
assert.strictEqual(observer.hasEditor({ resource: input1.resource, typeId: input1.typeId, editorId: input1.editorId }), true);
658+
assert.strictEqual(observer.hasEditor({ resource: input2.resource, typeId: input2.typeId, editorId: input2.editorId }), false);
659+
assert.strictEqual(observer.hasEditor({ resource: input3.resource, typeId: input3.typeId, editorId: input3.editorId }), true);
660+
assert.strictEqual(observer.hasEditor({ resource: input4.resource, typeId: input4.typeId, editorId: input4.editorId }), true);
661+
});
631662
});

src/vs/workbench/services/textfile/common/textFileEditorModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
674674
return this.dirty;
675675
}
676676

677+
isModified(): boolean {
678+
return this.isDirty();
679+
}
680+
677681
setDirty(dirty: boolean): void {
678682
if (!this.isResolved()) {
679683
return; // only resolved models can be marked dirty

src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ suite('Files - TextFileEditorModel', () => {
104104
model.updateTextEditorModel(createTextBufferFactory('bar'));
105105
assert.ok(getLastModifiedTime(model) <= Date.now());
106106
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
107+
assert.ok(model.isModified());
107108

108109
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
109110
assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true);
@@ -123,6 +124,7 @@ suite('Files - TextFileEditorModel', () => {
123124

124125
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
125126
assert.ok(!model.isDirty());
127+
assert.ok(!model.isModified());
126128
assert.ok(savedEvent);
127129
assert.ok((savedEvent as ITextFileEditorModelSaveEvent).stat);
128130
assert.strictEqual((savedEvent as ITextFileEditorModelSaveEvent).reason, SaveReason.AUTO);
@@ -182,6 +184,7 @@ suite('Files - TextFileEditorModel', () => {
182184

183185
assert.ok(model.hasState(TextFileEditorModelState.ERROR));
184186
assert.ok(model.isDirty());
187+
assert.ok(model.isModified());
185188
assert.ok(saveErrorEvent);
186189

187190
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
@@ -238,6 +241,7 @@ suite('Files - TextFileEditorModel', () => {
238241

239242
assert.ok(model.hasState(TextFileEditorModelState.ERROR));
240243
assert.ok(model.isDirty());
244+
assert.ok(model.isModified());
241245
assert.ok(saveErrorEvent);
242246

243247
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
@@ -268,6 +272,7 @@ suite('Files - TextFileEditorModel', () => {
268272

269273
assert.ok(model.hasState(TextFileEditorModelState.CONFLICT));
270274
assert.ok(model.isDirty());
275+
assert.ok(model.isModified());
271276
assert.ok(saveErrorEvent);
272277

273278
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
@@ -457,6 +462,7 @@ suite('Files - TextFileEditorModel', () => {
457462
await model.resolve();
458463
model.updateTextEditorModel(createTextBufferFactory('foo'));
459464
assert.ok(model.isDirty());
465+
assert.ok(model.isModified());
460466

461467
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
462468
assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true);
@@ -471,6 +477,7 @@ suite('Files - TextFileEditorModel', () => {
471477
model = accessor.workingCopyService.get(model) as TextFileEditorModel;
472478

473479
assert.strictEqual(model.isDirty(), false);
480+
assert.strictEqual(model.isModified(), false);
474481
assert.strictEqual(eventCounter, 1);
475482

476483
assert.ok(workingCopyEvent);
@@ -497,12 +504,14 @@ suite('Files - TextFileEditorModel', () => {
497504
await model.resolve();
498505
model.updateTextEditorModel(createTextBufferFactory('foo'));
499506
assert.ok(model.isDirty());
507+
assert.ok(model.isModified());
500508

501509
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
502510
assert.strictEqual(accessor.workingCopyService.isDirty(model.resource, model.typeId), true);
503511

504512
await model.revert({ soft: true });
505513
assert.strictEqual(model.isDirty(), false);
514+
assert.strictEqual(model.isModified(), false);
506515
assert.strictEqual(model.textEditorModel!.getValue(), 'foo');
507516
assert.strictEqual(eventCounter, 1);
508517

src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
243243
return this.dirty;
244244
}
245245

246+
isModified(): boolean {
247+
return this.isDirty();
248+
}
249+
246250
private setDirty(dirty: boolean): void {
247251
if (this.dirty === dirty) {
248252
return;

src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,27 @@ export class BrowserWorkingCopyBackupTracker extends WorkingCopyBackupTracker im
3232
protected onFinalBeforeShutdown(reason: ShutdownReason): boolean {
3333

3434
// Web: we cannot perform long running in the shutdown phase
35-
// As such we need to check sync if there are any dirty working
35+
// As such we need to check sync if there are any modified working
3636
// copies that have not been backed up yet and then prevent the
3737
// shutdown if that is the case.
3838

39-
const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies;
40-
if (!dirtyWorkingCopies.length) {
41-
return false; // no dirty: no veto
39+
const modifiedWorkingCopies = this.workingCopyService.modifiedWorkingCopies;
40+
if (!modifiedWorkingCopies.length) {
41+
return false; // nothing modified: no veto
4242
}
4343

4444
if (!this.filesConfigurationService.isHotExitEnabled) {
45-
return true; // dirty without backup: veto
45+
return true; // modified without backup: veto
4646
}
4747

48-
for (const dirtyWorkingCopy of dirtyWorkingCopies) {
49-
if (!this.workingCopyBackupService.hasBackupSync(dirtyWorkingCopy, this.getContentVersion(dirtyWorkingCopy))) {
48+
for (const modifiedWorkingCopy of modifiedWorkingCopies) {
49+
if (!this.workingCopyBackupService.hasBackupSync(modifiedWorkingCopy, this.getContentVersion(modifiedWorkingCopy))) {
5050
this.logService.warn('Unload veto: pending backups');
5151

52-
return true; // dirty without backup: veto
52+
return true; // modified without backup: veto
5353
}
5454
}
5555

56-
return false; // dirty with backups: no veto
56+
return false; // modified and backed up: no veto
5757
}
5858
}

src/vs/workbench/services/workingCopy/common/resourceWorkingCopy.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ export abstract class ResourceWorkingCopy extends Disposable implements IResourc
143143

144144
//#endregion
145145

146+
//#region Modified Tracking
147+
148+
isModified(): boolean {
149+
return this.isDirty();
150+
}
151+
152+
//#endregion
146153

147154
//#region Abstract
148155

0 commit comments

Comments
 (0)