From 0c0837460fc35806d26ea6205c862c1ef3c83c74 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:23:01 +0200 Subject: [PATCH] SCM - add support for multi-select in the history graph (#222186) --- extensions/git/src/commands.ts | 37 ++++++--- .../contrib/scm/browser/scmViewPane.ts | 75 +++++++++++++++---- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 5ba9b69d6be..e6e4e17e067 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -4192,17 +4192,33 @@ export class CommandCenter { } @command('git.viewCommit', { repository: true }) - async viewCommit(repository: Repository, historyItem: SourceControlHistoryItem): Promise { - if (!repository || !historyItem) { + async viewCommit(repository: Repository, historyItem1: SourceControlHistoryItem, historyItem2?: SourceControlHistoryItem): Promise { + if (!repository || !historyItem1) { return; } - const commit = await repository.getCommit(historyItem.id); - const title = `${historyItem.id.substring(0, 8)} - ${commit.message}`; + if (historyItem2) { + const mergeBase = await repository.getMergeBase(historyItem1.id, historyItem2.id); + if (!mergeBase || (mergeBase !== historyItem1.id && mergeBase !== historyItem2.id)) { + return; + } + } - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-commit' }); + const historyItemId = historyItem1.id; + const historyItemParentId = historyItem2 ? historyItem2.id : + historyItem1.parentIds.length > 0 ? historyItem1.parentIds[0] : `${historyItemId}^`; - await this._viewChanges(repository, historyItem, multiDiffSourceUri, title); + let title = ''; + if (!historyItem2) { + const commit = await repository.getCommit(historyItemId); + title = `${historyItemId.substring(0, 8)} - ${commit.message}`; + } else { + title = l10n.t('All Changes ({0} ↔ {1})', historyItemParentId.substring(0, 8), historyItemId.substring(0, 8)); + } + + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `${historyItemParentId}..${historyItemId}`, { scheme: 'git-commit', }); + + await this._viewChanges(repository, historyItemId, historyItemParentId, multiDiffSourceUri, title); } @command('git.viewAllChanges', { repository: true }) @@ -4217,18 +4233,17 @@ export class CommandCenter { const multiDiffSourceUri = toGitUri(Uri.file(repository.root), historyItem.id, { scheme: 'git-changes' }); - await this._viewChanges(repository, historyItem, multiDiffSourceUri, title); + await this._viewChanges(repository, modifiedShortRef, originalShortRef, multiDiffSourceUri, title); } - async _viewChanges(repository: Repository, historyItem: SourceControlHistoryItem, multiDiffSourceUri: Uri, title: string): Promise { - const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : `${historyItem.id}^`; - const changes = await repository.diffBetween(historyItemParentId, historyItem.id); + async _viewChanges(repository: Repository, historyItemId: string, historyItemParentId: string, multiDiffSourceUri: Uri, title: string): Promise { + const changes = await repository.diffBetween(historyItemParentId, historyItemId); if (changes.length === 0) { return; } - const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItem.id)); + const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItemId)); await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e5a12c5121e..b7cc7c8b5e7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -822,28 +822,66 @@ class HistoryItemGroupRenderer implements ICompressibleTreeRenderer { + protected override async runAction(action: IAction, context: SCMHistoryItemTreeElement): Promise { if (!(action instanceof MenuItemAction)) { return super.runAction(action, context); } const args: (ISCMProvider | ISCMHistoryItem)[] = []; - if (isSCMHistoryItemTreeElement(context)) { - args.push(context.historyItemGroup.repository.provider); - } else { - args.push(context.repository.provider); + args.push(context.historyItemGroup.repository.provider); + + args.push({ + id: context.id, + parentIds: context.parentIds, + message: context.message, + author: context.author, + icon: context.icon, + timestamp: context.timestamp, + statistics: context.statistics, + } satisfies ISCMHistoryItem); + + await action.run(...args); + } +} + +class HistoryItemActionRunner2 extends ActionRunner { + constructor(private readonly getSelectedHistoryItems: () => SCMHistoryItemViewModelTreeElement[]) { + super(); + } + + protected override async runAction(action: IAction, context: SCMHistoryItemViewModelTreeElement): Promise { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); } - const historyItem = isSCMHistoryItemTreeElement(context) ? context : context.historyItemViewModel.historyItem; - args.push({ - id: historyItem.id, - parentIds: historyItem.parentIds, - message: historyItem.message, - author: historyItem.author, - icon: historyItem.icon, - timestamp: historyItem.timestamp, - statistics: historyItem.statistics, - } satisfies ISCMHistoryItem); + const args: (ISCMProvider | ISCMHistoryItem)[] = []; + args.push(context.repository.provider); + + const selection = this.getSelectedHistoryItems(); + const contextIsSelected = selection.some(s => s === context); + if (contextIsSelected && selection.length > 1) { + args.push(...[selection[0], selection[selection.length - 1]] + .map(h => ( + { + id: h.historyItemViewModel.historyItem.id, + parentIds: h.historyItemViewModel.historyItem.parentIds, + message: h.historyItemViewModel.historyItem.message, + author: h.historyItemViewModel.historyItem.author, + icon: h.historyItemViewModel.historyItem.icon, + timestamp: h.historyItemViewModel.historyItem.timestamp, + statistics: h.historyItemViewModel.historyItem.statistics, + } satisfies ISCMHistoryItem))); + } else { + args.push({ + id: context.historyItemViewModel.historyItem.id, + parentIds: context.historyItemViewModel.historyItem.parentIds, + message: context.historyItemViewModel.historyItem.message, + author: context.historyItemViewModel.historyItem.author, + icon: context.historyItemViewModel.historyItem.icon, + timestamp: context.historyItemViewModel.historyItem.timestamp, + statistics: context.historyItemViewModel.historyItem.statistics, + } satisfies ISCMHistoryItem); + } await action.run(...args); } @@ -3517,7 +3555,7 @@ export class SCMViewPane extends ViewPane { const menus = this.scmViewService.menus.getRepositoryMenus(element.repository.provider); const menu = menus.historyProviderMenu?.getHistoryItemMenu2(element); if (menu) { - actionRunner = new HistoryItemActionRunner(); + actionRunner = new HistoryItemActionRunner2(() => this.getSelectedHistoryItems()); actions = collectContextMenuActions(menu); } } @@ -3544,6 +3582,11 @@ export class SCMViewPane extends ViewPane { .filter(r => !!r && !isSCMResourceGroup(r))! as any; } + private getSelectedHistoryItems(): SCMHistoryItemViewModelTreeElement[] { + return this.tree.getSelection() + .filter(r => !!r && isSCMHistoryItemViewModelTreeElement(r))!; + } + private getViewMode(): ViewMode { let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewMode.List : ViewMode.Tree; const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewMode;