From 4261e7ccd2e4eacf00af9f3a961df2e33035ed76 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 16 Mar 2022 07:56:34 +0100 Subject: [PATCH] history - allow to rename entries --- .../browser/localHistoryCommands.ts | 42 ++++++++++++++- .../browser/localHistoryTimeline.ts | 1 + .../browser/workingCopyHistoryService.ts | 9 +++- .../workingCopy/common/workingCopyHistory.ts | 12 ++++- .../common/workingCopyHistoryService.ts | 31 ++++++++++- .../workingCopyHistoryService.test.ts | 51 +++++++++++++++++++ 6 files changed, 142 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts index 9a49861c6c2..6253ce5a10f 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts @@ -21,6 +21,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; import { Codicon } from 'vs/base/common/codicons'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export const LOCAL_HISTORY_MENU_CONTEXT_VALUE = 'localHistory:item'; export const LOCAL_HISTORY_MENU_CONTEXT_KEY = ContextKeyExpr.equals('timelineItem', LOCAL_HISTORY_MENU_CONTEXT_VALUE); @@ -230,6 +231,45 @@ async function restore(accessor: ServicesAccessor, item: ITimelineCommandArgumen //#endregion +//#region Rename + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.localHistory.rename', + title: { value: localize('localHistory.rename', "Rename"), original: 'Rename' }, + menu: { + id: MenuId.TimelineItemContext, + group: '3_edit', + order: 1, + when: LOCAL_HISTORY_MENU_CONTEXT_KEY + } + }); + } + async run(accessor: ServicesAccessor, item: ITimelineCommandArgument): Promise { + const workingCopyHistoryService = accessor.get(IWorkingCopyHistoryService); + const quickInputService = accessor.get(IQuickInputService); + + const { entry } = await findLocalHistoryEntry(workingCopyHistoryService, item); + if (entry) { + const inputBox = quickInputService.createInputBox(); + inputBox.title = localize('renameLocalHistoryEntryTitle', "Rename Local History Entry"); + inputBox.ignoreFocusOut = true; + inputBox.placeholder = localize('renameLocalHistoryPlaceholder', "Enter the new name of the local history entry"); + inputBox.value = SaveSourceRegistry.getSourceLabel(entry.source) ?? entry.source; + inputBox.show(); + inputBox.onDidAccept(() => { + if (inputBox.value) { + workingCopyHistoryService.updateEntry(entry, { source: inputBox.value }, CancellationToken.None); + } + inputBox.dispose(); + }); + } + } +}); + +//#endregion + //#region Delete registerAction2(class extends Action2 { @@ -240,7 +280,7 @@ registerAction2(class extends Action2 { menu: { id: MenuId.TimelineItemContext, group: '3_edit', - order: 1, + order: 2, when: LOCAL_HISTORY_MENU_CONTEXT_KEY } }); diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts b/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts index d9e4ed99d25..ea600e02359 100644 --- a/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts +++ b/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts @@ -78,6 +78,7 @@ export class LocalHistoryTimeline extends Disposable implements IWorkbenchContri // History changes this._register(this.workingCopyHistoryService.onDidAddEntry(e => this.onDidChangeWorkingCopyHistoryEntry(e.entry, false /* entry added */))); + this._register(this.workingCopyHistoryService.onDidChangeEntry(e => this.onDidChangeWorkingCopyHistoryEntry(e.entry, false /* entry changed */))); this._register(this.workingCopyHistoryService.onDidRemoveEntry(e => this.onDidChangeWorkingCopyHistoryEntry(e.entry, true /* entry removed */))); this._register(this.workingCopyHistoryService.onDidRemoveAllEntries(() => this.onDidChangeWorkingCopyHistoryEntry(undefined /* all history */, true /* entry removed */))); diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts index d95cb71d889..7048ae8eb23 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts @@ -28,6 +28,13 @@ class BrowserWorkingCopyHistoryModel extends WorkingCopyHistoryModel { return entry; } + override async updateEntry(entry: IWorkingCopyHistoryEntry, properties: { source: SaveSource }, token: CancellationToken): Promise { + await super.updateEntry(entry, properties, token); + if (!token.isCancellationRequested) { + await this.store(); // need to store on each remove because we do not have long running shutdown support in web + } + } + override async removeEntry(entry: IWorkingCopyHistoryEntry, token: CancellationToken): Promise { const removed = await super.removeEntry(entry, token); if (removed && !token.isCancellationRequested) { @@ -53,7 +60,7 @@ export class BrowserWorkingCopyHistoryService extends WorkingCopyHistoryService } protected override createModel(resource: URI, historyHome: URI): WorkingCopyHistoryModel { - return new BrowserWorkingCopyHistoryModel(resource, historyHome, this._onDidAddEntry, this._onDidRemoveEntry, this.fileService, this.labelService, this.logService, this.configurationService); + return new BrowserWorkingCopyHistoryModel(resource, historyHome, this._onDidAddEntry, this._onDidChangeEntry, this._onDidRemoveEntry, this.fileService, this.labelService, this.logService, this.configurationService); } } diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts b/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts index c667472856a..2c10c1a36a4 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyHistory.ts @@ -58,7 +58,7 @@ export interface IWorkingCopyHistoryEntry { /** * Associated source with the history entry. */ - readonly source: SaveSource; + source: SaveSource; } export interface IWorkingCopyHistoryEntryDescriptor { @@ -90,6 +90,11 @@ export interface IWorkingCopyHistoryService { */ onDidAddEntry: Event; + /** + * An event when entries are changed in the history. + */ + onDidChangeEntry: Event; + /** * An event when entries are removed from the history. */ @@ -106,6 +111,11 @@ export interface IWorkingCopyHistoryService { */ addEntry(descriptor: IWorkingCopyHistoryEntryDescriptor, token: CancellationToken): Promise; + /** + * Updates an entry in the local history if found. + */ + updateEntry(entry: IWorkingCopyHistoryEntry, properties: { source: SaveSource }, token: CancellationToken): Promise; + /** * Removes an entry from the local history if found. */ diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts index 2befd87472c..9cdc1f29783 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts @@ -64,6 +64,7 @@ export class WorkingCopyHistoryModel { private readonly workingCopyResource: URI, private readonly historyHome: URI, private readonly entryAddedEmitter: Emitter, + private readonly entryChangedEmitter: Emitter, private readonly entryRemovedEmitter: Emitter, private readonly fileService: IFileService, private readonly labelService: ILabelService, @@ -125,6 +126,19 @@ export class WorkingCopyHistoryModel { return true; } + async updateEntry(entry: IWorkingCopyHistoryEntry, properties: { source: SaveSource }, token: CancellationToken): Promise { + const index = this.entries.indexOf(entry); + if (index === -1) { + return; + } + + // Update entry + entry.source = properties.source; + + // Events + this.entryChangedEmitter.fire({ entry }); + } + async getEntries(): Promise { // Make sure to await resolving when all entries are asked for @@ -322,6 +336,9 @@ export abstract class WorkingCopyHistoryService extends Disposable implements IW protected readonly _onDidAddEntry = this._register(new Emitter()); readonly onDidAddEntry = this._onDidAddEntry.event; + protected readonly _onDidChangeEntry = this._register(new Emitter()); + readonly onDidChangeEntry = this._onDidChangeEntry.event; + protected readonly _onDidRemoveEntry = this._register(new Emitter()); readonly onDidRemoveEntry = this._onDidRemoveEntry.event; @@ -382,6 +399,18 @@ export abstract class WorkingCopyHistoryService extends Disposable implements IW return model.addEntry(source, timestamp, token); } + async updateEntry(entry: IWorkingCopyHistoryEntry, properties: { source: SaveSource }, token: CancellationToken): Promise { + + // Resolve history model for working copy + const model = await this.getModel(entry.workingCopy.resource); + if (token.isCancellationRequested) { + return; + } + + // Rename in model + return model.updateEntry(entry, properties, token); + } + async removeEntry(entry: IWorkingCopyHistoryEntry, token: CancellationToken): Promise { // Resolve history model for working copy @@ -433,7 +462,7 @@ export abstract class WorkingCopyHistoryService extends Disposable implements IW } protected createModel(resource: URI, historyHome: URI): WorkingCopyHistoryModel { - return new WorkingCopyHistoryModel(resource, historyHome, this._onDidAddEntry, this._onDidRemoveEntry, this.fileService, this.labelService, this.logService, this.configurationService); + return new WorkingCopyHistoryModel(resource, historyHome, this._onDidAddEntry, this._onDidChangeEntry, this._onDidRemoveEntry, this.fileService, this.labelService, this.logService, this.configurationService); } } diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts index 07ff7c4ef6b..714edaa807f 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts @@ -173,6 +173,42 @@ flakySuite('WorkingCopyHistoryService', () => { assert.strictEqual(addEvents.length, 4); }); + test('renameEntry', async () => { + let changeEvents: IWorkingCopyHistoryEvent[] = []; + service.onDidChangeEntry(e => changeEvents.push(e)); + + const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + + const entry = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); + await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); + await addEntry({ resource: workingCopy1.resource, source: 'My Source' }, CancellationToken.None); + + let entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); + assert.strictEqual(entries.length, 3); + + await service.updateEntry(entry, { source: 'Hello Rename' }, CancellationToken.None); + + assert.strictEqual(changeEvents.length, 1); + assert.strictEqual(changeEvents[0].entry, entry); + + entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); + assert.strictEqual(entries[0].source, 'Hello Rename'); + + // Simulate shutdown + const event = new TestWillShutdownEvent(); + service._lifecycleService.fireWillShutdown(event); + await Promise.allSettled(event.value); + + // Resolve from disk fresh and verify again + + service.dispose(); + service = new TestWorkingCopyHistoryService(testDir); + + entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); + assert.strictEqual(entries.length, 3); + assert.strictEqual(entries[0].source, 'Hello Rename'); + }); + test('removeEntry', async () => { let removeEvents: IWorkingCopyHistoryEvent[] = []; service.onDidRemoveEntry(e => removeEvents.push(e)); @@ -199,6 +235,19 @@ flakySuite('WorkingCopyHistoryService', () => { entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 3); + + // Simulate shutdown + const event = new TestWillShutdownEvent(); + service._lifecycleService.fireWillShutdown(event); + await Promise.allSettled(event.value); + + // Resolve from disk fresh and verify again + + service.dispose(); + service = new TestWorkingCopyHistoryService(testDir); + + entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); + assert.strictEqual(entries.length, 3); }); test('removeAll', async () => { @@ -220,6 +269,8 @@ flakySuite('WorkingCopyHistoryService', () => { await service.removeAll(CancellationToken.None); + assert.strictEqual(removed, true); + entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 0); entries = await service.getEntries(workingCopy2.resource, CancellationToken.None);