diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index cd32a38c1aa..b5c735586cc 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, l10n, LogOutputChannel } from 'vscode'; +import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel } from 'vscode'; import { Repository, Resource } from './repository'; import { IDisposable, filterEvent } from './util'; import { toGitUri } from './uri'; @@ -84,11 +84,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const historyItems: SourceControlHistoryItem[] = []; const commits = await this.repository.log({ range: `${refParentId}..${refId}`, shortStats: true, sortByAuthorDate: true }); - if (commits.length >= 2) { - const allChanges = await this.repository.diffBetweenShortStat(refParentId, refId); - historyItems.push({ id: refId, parentIds: [refParentId], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics: allChanges }); - } - await ensureEmojis(); historyItems.push(...commits.map(commit => { @@ -109,6 +104,16 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItems; } + async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { + if (!historyItemParentId) { + const commit = await this.repository.getCommit(historyItemId); + historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : `${historyItemId}^`; + } + + const allChanges = await this.repository.diffBetweenShortStat(historyItemParentId, historyItemId); + return { id: historyItemId, parentIds: [historyItemParentId], label: '', statistics: allChanges }; + } + async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { if (!historyItemParentId) { const commit = await this.repository.getCommit(historyItemId); diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 6e4d467c126..81a43a35387 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -192,6 +192,11 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { return historyItems?.map(historyItem => ({ ...historyItem, icon: getIconFromIconDto(historyItem.icon) })); } + async provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise { + const historyItem = await this.proxy.$provideHistoryItemSummary(this.handle, historyItemId, historyItemParentId, CancellationToken.None); + return historyItem ? { ...historyItem, icon: getIconFromIconDto(historyItem.icon) } : undefined; + } + async provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise { const changes = await this.proxy.$provideHistoryItemChanges(this.handle, historyItemId, historyItemParentId, CancellationToken.None); return changes?.map(change => ({ diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 1520b5afe25..346c1ddd264 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2224,6 +2224,7 @@ export interface ExtHostSCMShape { $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; $provideHistoryItems(sourceControlHandle: number, historyItemGroupId: string, options: any, token: CancellationToken): Promise; + $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItemGroupBase(sourceControlHandle: number, historyItemGroupId: string, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupId1: string, historyItemGroupId2: string, token: CancellationToken): Promise<{ id: string; ahead: number; behind: number } | undefined>; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 5576c0be0cf..3e40a0917ae 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -977,6 +977,16 @@ export class ExtHostSCM implements ExtHostSCMShape { return historyItems?.map(item => ({ ...item, icon: getHistoryItemIconDto(item) })) ?? undefined; } + async $provideHistoryItemSummary(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + if (typeof historyProvider?.provideHistoryItemSummary !== 'function') { + return undefined; + } + + const historyItem = await historyProvider.provideHistoryItemSummary(historyItemId, historyItemParentId, token); + return historyItem ? { ...historyItem, icon: getHistoryItemIconDto(historyItem) } : undefined; + } + async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; return await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId, token) ?? undefined; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index f5cf500d5be..8fae0d843b8 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -321,6 +321,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis ], description: localize('scm.showOutgoingChanges', "Controls whether outgoing changes are shown in the Source Control view."), default: 'auto' + }, + 'scm.showChangesSummary': { + type: 'boolean', + description: localize('scm.showChangesSummary', "Controls whether the All Changes entry is shown for incoming/outgoing changes in the Source Control view."), + default: true } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 058a7572889..73ea9f1095f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -26,7 +26,7 @@ import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator } from './util'; import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; -import { IConfigurationService, ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { disposableTimeout, Sequencer, ThrottledDelayer, Throttler } from 'vs/base/common/async'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; @@ -2525,18 +2525,6 @@ export class SCMViewPane extends ViewPane { private readonly _onDidChangeViewSortKey = new Emitter(); readonly onDidChangeViewSortKey = this._onDidChangeViewSortKey.event; - private _showActionButton = false; - get showActionButton(): boolean { return this._showActionButton; } - - private _alwaysShowRepositories = false; - get alwaysShowRepositories(): boolean { return this._alwaysShowRepositories; } - - private _showIncomingChanges: ShowChangesSetting | undefined; - get showIncomingChanges(): ShowChangesSetting { return this._showIncomingChanges ?? 'never'; } - - private _showOutgoingChanges: ShowChangesSetting | undefined; - get showOutgoingChanges(): ShowChangesSetting { return this._showOutgoingChanges ?? 'never'; } - private readonly items = new DisposableMap(); private readonly visibilityDisposables = new DisposableStore(); @@ -2663,28 +2651,25 @@ export class SCMViewPane extends ViewPane { if (visible) { await this.tree.setInput(this.scmViewService, viewState); - const onDidChangeConfiguration = (e?: IConfigurationChangeEvent) => { - if (!e || - e.affectsConfiguration('scm.showActionButton') || - e.affectsConfiguration('scm.alwaysShowRepositories') || - e.affectsConfiguration('scm.showIncomingChanges') || - e.affectsConfiguration('scm.showOutgoingChanges') || - e.affectsConfiguration('scm.inputMinLineCount') || - e.affectsConfiguration('scm.inputMaxLineCount')) { - this._showActionButton = this.configurationService.getValue('scm.showActionButton'); - this._alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories'); - this._showIncomingChanges = this.configurationService.getValue('scm.showIncomingChanges'); - this._showOutgoingChanges = this.configurationService.getValue('scm.showOutgoingChanges'); - - if (e?.affectsConfiguration('scm.alwaysShowRepositories')) { - this.updateActions(); - } - + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.alwaysShowRepositories'), + this.visibilityDisposables) + (() => { + this.updateActions(); this.updateChildren(); - } - }; - this.configurationService.onDidChangeConfiguration(onDidChangeConfiguration, this, this.visibilityDisposables); - onDidChangeConfiguration(); + }, this, this.visibilityDisposables); + + Event.filter(this.configurationService.onDidChangeConfiguration, + e => + e.affectsConfiguration('scm.inputMinLineCount') || + e.affectsConfiguration('scm.inputMaxLineCount') || + e.affectsConfiguration('scm.showActionButton') || + e.affectsConfiguration('scm.showChangesSummary') || + e.affectsConfiguration('scm.showIncomingChanges') || + e.affectsConfiguration('scm.showOutgoingChanges'), + this.visibilityDisposables) + (() => this.updateChildren(), this, this.visibilityDisposables); // Add visible repositories this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); @@ -2728,7 +2713,7 @@ export class SCMViewPane extends ViewPane { historyItemActionRunner.onWillRun(() => this.tree.domFocus(), this, this.disposables); this.disposables.add(historyItemActionRunner); - const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton, () => this.showIncomingChanges, () => this.showOutgoingChanges); + const treeDataSource = this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode); this.disposables.add(treeDataSource); this.tree = this.instantiationService.createInstance( @@ -3113,7 +3098,9 @@ export class SCMViewPane extends ViewPane { } private updateScmProviderContextKeys(): void { - if (!this.alwaysShowRepositories && this.items.size === 1) { + const alwaysShowRepositories = this.configurationService.getValue('scm.alwaysShowRepositories'); + + if (!alwaysShowRepositories && this.items.size === 1) { const provider = Iterable.first(this.items.keys())!.provider; this.scmProviderContextKey.set(provider.contextValue); this.scmProviderRootUriContextKey.set(provider.rootUri?.toString()); @@ -3197,13 +3184,16 @@ class SCMTreeDataSource implements IAsyncDataSource ViewMode, - private readonly alwaysShowRepositories: () => boolean, - private readonly showActionButton: () => boolean, - private readonly showIncomingChanges: () => ShowChangesSetting, - private readonly showOutgoingChanges: () => ShowChangesSetting, + @IConfigurationService private readonly configurationService: IConfigurationService, @ISCMViewService private readonly scmViewService: ISCMViewService, @IUriIdentityService private uriIdentityService: IUriIdentityService, ) { + const onDidChangeConfiguration = Event.filter( + this.configurationService.onDidChangeConfiguration, + e => e.affectsConfiguration('scm.showChangesSummary'), + this.disposables); + this.disposables.add(onDidChangeConfiguration(() => this.historyProviderCache.clear())); + this.scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.disposables); this.onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); } @@ -3237,8 +3227,8 @@ class SCMTreeDataSource implements IAsyncDataSource> { + const { alwaysShowRepositories, showActionButton, showIncomingChanges, showOutgoingChanges } = this.getConfiguration(); const repositoryCount = this.scmViewService.visibleRepositories.length; - const alwaysShowRepositories = this.alwaysShowRepositories(); if (isSCMViewService(inputOrElement) && (repositoryCount > 1 || alwaysShowRepositories)) { return this.scmViewService.visibleRepositories; @@ -3248,7 +3238,6 @@ class SCMTreeDataSource implements IAsyncDataSource { + const { showIncomingChanges, showOutgoingChanges } = this.getConfiguration(); + const scmProvider = element.provider; const historyProvider = scmProvider.historyProvider; const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; - if (!historyProvider || !currentHistoryItemGroup || (this.showIncomingChanges() === 'never' && this.showOutgoingChanges() === 'never')) { + if (!historyProvider || !currentHistoryItemGroup || (showIncomingChanges === 'never' && showOutgoingChanges === 'never')) { return []; } @@ -3347,8 +3338,8 @@ class SCMTreeDataSource implements IAsyncDataSource 0))) { + (showIncomingChanges === 'always' || + (showIncomingChanges === 'auto' && (historyItemGroupDetails.incoming.count ?? 0) > 0))) { children.push({ ...historyItemGroupDetails.incoming, ariaLabel: localize('incomingChangesAriaLabel', "Incoming changes from {0}", historyItemGroupDetails.incoming.label), @@ -3359,8 +3350,8 @@ class SCMTreeDataSource implements IAsyncDataSource 0))) { + (showOutgoingChanges === 'always' || + (showOutgoingChanges === 'auto' && (historyItemGroupDetails.outgoing.count ?? 0) > 0))) { children.push({ ...historyItemGroupDetails.outgoing, ariaLabel: localize('outgoingChangesAriaLabel', "Outgoing changes from {0}", historyItemGroupDetails.outgoing.label), @@ -3386,6 +3377,16 @@ class SCMTreeDataSource implements IAsyncDataSource= 2) { + const allChanges = await historyProvider.provideHistoryItemSummary(element.id, element.ancestor); + if (allChanges) { + historyItems.splice(0, 0, { ...allChanges, icon: allChanges.icon ?? Codicon.files, label: localize('allChanges', "All Changes") }); + } + } + this.historyProviderCache.set(repository, { ...historyProviderCacheEntry, historyItems: historyItemsMap.set(element.id, historyItems) @@ -3481,6 +3482,22 @@ class SCMTreeDataSource implements IAsyncDataSource('scm.alwaysShowRepositories'), + showActionButton: this.configurationService.getValue('scm.showActionButton'), + showChangesSummary: this.configurationService.getValue('scm.showChangesSummary'), + showIncomingChanges: this.configurationService.getValue('scm.showIncomingChanges'), + showOutgoingChanges: this.configurationService.getValue('scm.showOutgoingChanges') + }; + } + private onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { // Added repositories for (const repository of added) { diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index db2e8ed4bf7..eaf365e1972 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -21,6 +21,7 @@ export interface ISCMHistoryProvider { set currentHistoryItemGroup(historyItemGroup: ISCMHistoryItemGroup | undefined); provideHistoryItems(historyItemGroupId: string, options: ISCMHistoryOptions): Promise; + provideHistoryItemSummary(historyItemId: string, historyItemParentId: string | undefined): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; resolveHistoryItemGroupBase(historyItemGroupId: string): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupId1: string, historyItemGroupId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined>; diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 8dd3dd899df..b030e44ee98 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -25,6 +25,7 @@ declare module 'vscode' { // onDidChangeHistoryItemGroups: Event; provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; + provideHistoryItemSummary?(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; resolveHistoryItemGroupBase(historyItemGroupId: string, token: CancellationToken): ProviderResult;