diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 7d79e128844..6190c2a538b 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, LogOutputChannel, SourceControlHistoryItemLabel } from 'vscode'; +import { Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryChangeEvent, SourceControlHistoryItemRef, l10n } from 'vscode'; import { Repository, Resource } from './repository'; import { IDisposable, dispose } from './util'; import { toGitUri } from './uri'; @@ -17,6 +17,9 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter(); readonly onDidChangeCurrentHistoryItemGroup: Event = this._onDidChangeCurrentHistoryItemGroup.event; + private readonly _onDidChangeHistory = new EventEmitter(); + readonly onDidChangeHistory: Event = this._onDidChangeHistory.event; + private readonly _onDidChangeDecorations = new EventEmitter(); readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; @@ -28,12 +31,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } private historyItemDecorations = new Map(); - private historyItemLabels = new Map([ - ['HEAD -> refs/heads/', new ThemeIcon('target')], - ['tag: refs/tags/', new ThemeIcon('tag')], - ['refs/heads/', new ThemeIcon('git-branch')], - ['refs/remotes/', new ThemeIcon('cloud')], - ]); private disposables: Disposable[] = []; @@ -85,6 +82,51 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec this.logger.trace(`[GitHistoryProvider][onDidRunGitStatus] currentHistoryItemGroup: ${JSON.stringify(this.currentHistoryItemGroup)}`); } + async provideHistoryItemRefs(): Promise { + const refs = await this.repository.getRefs(); + + const branches: SourceControlHistoryItemRef[] = []; + const remoteBranches: SourceControlHistoryItemRef[] = []; + const tags: SourceControlHistoryItemRef[] = []; + + for (const ref of refs) { + switch (ref.type) { + case RefType.RemoteHead: + remoteBranches.push({ + id: `refs/remotes/${ref.remote}/${ref.name}`, + name: ref.name ?? '', + description: ref.commit ? l10n.t('Remote branch at {0}', ref.commit.substring(0, 8)) : undefined, + revision: ref.commit, + icon: new ThemeIcon('cloud'), + category: l10n.t('remote branches') + }); + break; + case RefType.Tag: + tags.push({ + id: `refs/tags/${ref.name}`, + name: ref.name ?? '', + description: ref.commit ? l10n.t('Tag at {0}', ref.commit.substring(0, 8)) : undefined, + revision: ref.commit, + icon: new ThemeIcon('tag'), + category: l10n.t('tags') + }); + break; + default: + branches.push({ + id: `refs/heads/${ref.name}`, + name: ref.name ?? '', + description: ref.commit ? ref.commit.substring(0, 8) : undefined, + revision: ref.commit, + icon: new ThemeIcon('git-branch'), + category: l10n.t('branches') + }); + break; + } + } + + return [...branches, ...remoteBranches, ...tags]; + } + async provideHistoryItems(options: SourceControlHistoryOptions): Promise { if (!this.currentHistoryItemGroup || !options.historyItemGroupIds) { return []; @@ -115,7 +157,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec await ensureEmojis(); return commits.map(commit => { - const labels = this.resolveHistoryItemLabels(commit); + const references = this.resolveHistoryItemRefs(commit); return { id: commit.hash, @@ -126,7 +168,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec displayId: commit.hash.substring(0, 8), timestamp: commit.authorDate?.getTime(), statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 }, - labels: labels.length !== 0 ? labels : undefined + references: references.length !== 0 ? references : undefined }; }); } catch (err) { @@ -208,19 +250,47 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return this.historyItemDecorations.get(uri.toString()); } - private resolveHistoryItemLabels(commit: Commit): SourceControlHistoryItemLabel[] { - const labels: SourceControlHistoryItemLabel[] = []; + private resolveHistoryItemRefs(commit: Commit): SourceControlHistoryItemRef[] { + const references: SourceControlHistoryItemRef[] = []; - for (const label of commit.refNames) { - for (const [key, value] of this.historyItemLabels) { - if (label.startsWith(key)) { - labels.push({ title: label.substring(key.length), icon: value }); + for (const ref of commit.refNames) { + switch (true) { + case ref.startsWith('HEAD -> refs/heads/'): + references.push({ + id: ref.substring('HEAD -> '.length), + name: ref.substring('HEAD -> refs/heads/'.length), + revision: commit.hash, + icon: new ThemeIcon('target') + }); + break; + case ref.startsWith('tag: refs/tags/'): + references.push({ + id: ref.substring('tag: '.length), + name: ref.substring('tag: refs/tags/'.length), + revision: commit.hash, + icon: new ThemeIcon('tag') + }); + break; + case ref.startsWith('refs/heads/'): + references.push({ + id: ref, + name: ref.substring('refs/heads/'.length), + revision: commit.hash, + icon: new ThemeIcon('git-branch') + }); + break; + case ref.startsWith('refs/remotes/'): + references.push({ + id: ref, + name: ref.substring('refs/remotes/'.length), + revision: commit.hash, + icon: new ThemeIcon('cloud') + }); break; - } } } - return labels; + return references; } private async resolveHEADMergeBase(): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 221202313bd..232a052bb7e 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -6,7 +6,7 @@ import { Barrier } from '../../../base/common/async.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { Event, Emitter } from '../../../base/common/event.js'; -import { observableValue, observableValueOpts } from '../../../base/common/observable.js'; +import { derivedOpts, observableValue, observableValueOpts } from '../../../base/common/observable.js'; import { IDisposable, DisposableStore, combinedDisposable, dispose, Disposable } from '../../../base/common/lifecycle.js'; import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType, ISCMActionButtonDescriptor } from '../../contrib/scm/common/scm.js'; import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, SCMHistoryItemGroupDto, SCMHistoryItemDto } from '../common/extHost.protocol.js'; @@ -17,7 +17,7 @@ import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { IQuickDiffService, QuickDiffProvider } from '../../contrib/scm/common/quickDiff.js'; -import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryOptions, ISCMHistoryProvider } from '../../contrib/scm/common/history.js'; +import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup, ISCMHistoryItemRef, ISCMHistoryOptions, ISCMHistoryProvider } from '../../contrib/scm/common/history.js'; import { ResourceTree } from '../../../base/common/resourceTree.js'; import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js'; import { IWorkspaceContextService } from '../../../platform/workspace/common/workspace.js'; @@ -28,6 +28,8 @@ import { ITextModelContentProvider, ITextModelService } from '../../../editor/co import { Schemas } from '../../../base/common/network.js'; import { ITextModel } from '../../../editor/common/model.js'; import { structuralEquals } from '../../../base/common/equals.js'; +import { Codicon } from '../../../base/common/codicons.js'; +import { historyItemGroupBase, historyItemGroupLocal, historyItemGroupRemote } from '../../contrib/scm/browser/scmHistory.js'; function getIconFromIconDto(iconDto?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon): URI | { light: URI; dark: URI } | ThemeIcon | undefined { if (iconDto === undefined) { @@ -43,15 +45,15 @@ function getIconFromIconDto(iconDto?: UriComponents | { light: UriComponents; da } function toISCMHistoryItem(historyItemDto: SCMHistoryItemDto): ISCMHistoryItem { - const labels = historyItemDto.labels?.map(l => ({ - title: l.title, icon: getIconFromIconDto(l.icon) + const references = historyItemDto.references?.map(r => ({ + ...r, icon: getIconFromIconDto(r.icon) })); const newLineIndex = historyItemDto.message.indexOf('\n'); const subject = newLineIndex === -1 ? historyItemDto.message : `${historyItemDto.message.substring(0, newLineIndex)}\u2026`; - return { ...historyItemDto, subject, labels }; + return { ...historyItemDto, subject, references }; } class SCMInputBoxContentProvider extends Disposable implements ITextModelContentProvider { @@ -171,12 +173,62 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider { }, undefined); get currentHistoryItemGroup() { return this._currentHistoryItemGroup; } + readonly currentHistoryItemRef = derivedOpts({ + owner: this, + equalsFn: structuralEquals + }, reader => { + const currentHistoryItemGroup = this._currentHistoryItemGroup.read(reader); + + return currentHistoryItemGroup ? { + id: currentHistoryItemGroup.id ?? '', + name: currentHistoryItemGroup.name, + revision: currentHistoryItemGroup.revision, + color: historyItemGroupLocal, + icon: Codicon.target, + } : undefined; + }); + + readonly currentHistoryItemRemoteRef = derivedOpts({ + owner: this, + equalsFn: structuralEquals + }, reader => { + const currentHistoryItemGroup = this._currentHistoryItemGroup.read(reader); + + return currentHistoryItemGroup?.remote ? { + id: currentHistoryItemGroup.remote.id ?? '', + name: currentHistoryItemGroup.remote.name, + revision: currentHistoryItemGroup.remote.revision, + color: historyItemGroupRemote, + icon: Codicon.cloud, + } : undefined; + }); + + readonly currentHistoryItemBaseRef = derivedOpts({ + owner: this, + equalsFn: structuralEquals + }, reader => { + const currentHistoryItemGroup = this._currentHistoryItemGroup.read(reader); + + return currentHistoryItemGroup?.base ? { + id: currentHistoryItemGroup.base.id ?? '', + name: currentHistoryItemGroup.base.name, + revision: currentHistoryItemGroup.base.revision, + color: historyItemGroupBase, + icon: Codicon.cloud, + } : undefined; + }); + constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { } async resolveHistoryItemGroupCommonAncestor(historyItemGroupIds: string[]): Promise { return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupIds, CancellationToken.None); } + async provideHistoryItemRefs(): Promise { + const historyItemRefs = await this.proxy.$provideHistoryItemRefs(this.handle, CancellationToken.None); + return historyItemRefs?.map(ref => ({ ...ref, icon: getIconFromIconDto(ref.icon) })); + } + async provideHistoryItems(options: ISCMHistoryOptions): Promise { const historyItems = await this.proxy.$provideHistoryItems(this.handle, options, CancellationToken.None); return historyItems?.map(historyItem => toISCMHistoryItem(historyItem)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0a34cf85f18..8b4cdc0a075 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1550,6 +1550,15 @@ export interface SCMHistoryItemGroupDto { readonly remote?: Omit, 'remote'>; } +export interface SCMHistoryItemRefDto { + readonly id: string; + readonly name: string; + readonly revision?: string; + readonly category?: string; + readonly description?: string; + readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; +} + export interface SCMHistoryItemDto { readonly id: string; readonly parentIds: string[]; @@ -1562,10 +1571,7 @@ export interface SCMHistoryItemDto { readonly insertions: number; readonly deletions: number; }; - readonly labels?: { - readonly title: string; - readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; - }[]; + readonly references?: SCMHistoryItemRefDto[]; } export interface SCMHistoryItemChangeDto { @@ -2358,6 +2364,7 @@ export interface ExtHostSCMShape { $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise; $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>; $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; + $provideHistoryItemRefs(sourceControlHandle: number, token: CancellationToken): Promise; $provideHistoryItems(sourceControlHandle: number, options: any, token: CancellationToken): Promise; $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise; $resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupIds: string[], token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 75453dbe3f2..a600b14bd04 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -11,7 +11,7 @@ import { debounce } from '../../../base/common/decorators.js'; import { DisposableStore, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js'; import { asPromise } from '../../../base/common/async.js'; import { ExtHostCommands } from './extHostCommands.js'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto } from './extHost.protocol.js'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemRefDto } from './extHost.protocol.js'; import { sortedDiff, equals } from '../../../base/common/arrays.js'; import { comparePaths } from '../../../base/common/comparers.js'; import type * as vscode from 'vscode'; @@ -72,11 +72,11 @@ function getHistoryItemIconDto(icon: vscode.Uri | { light: vscode.Uri; dark: vsc } function toSCMHistoryItemDto(historyItem: vscode.SourceControlHistoryItem): SCMHistoryItemDto { - const labels = historyItem.labels?.map(l => ({ - title: l.title, icon: getHistoryItemIconDto(l.icon) + const references = historyItem.references?.map(r => ({ + ...r, icon: getHistoryItemIconDto(r.icon) })); - return { ...historyItem, labels }; + return { ...historyItem, references }; } function compareResourceThemableDecorations(a: vscode.SourceControlResourceThemableDecorations, b: vscode.SourceControlResourceThemableDecorations): number { @@ -982,6 +982,13 @@ export class ExtHostSCM implements ExtHostSCMShape { return await historyProvider?.resolveHistoryItemGroupCommonAncestor(historyItemGroupIds, token) ?? undefined; } + async $provideHistoryItemRefs(sourceControlHandle: number, token: CancellationToken): Promise { + const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; + const historyItemRefs = await historyProvider?.provideHistoryItemRefs(token); + + return historyItemRefs?.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) })) ?? undefined; + } + async $provideHistoryItems(sourceControlHandle: number, options: any, token: CancellationToken): Promise { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; const historyItems = await historyProvider?.provideHistoryItems(options, token); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index f41adfd5435..eb98f680a0b 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -495,13 +495,15 @@ display: flex; } -.monaco-toolbar .action-label.scm-graph-repository-picker { +.monaco-toolbar .action-label.scm-graph-repository-picker, +.monaco-toolbar .action-label.scm-graph-history-item-picker { align-items: center; font-weight: normal; line-height: 16px; } -.monaco-toolbar .action-label.scm-graph-repository-picker .codicon { +.monaco-toolbar .action-label.scm-graph-repository-picker .codicon, +.monaco-toolbar .action-label.scm-graph-history-item-picker .codicon { font-size: 14px; } @@ -558,7 +560,7 @@ .scm-history-view .history-item-load-more .history-item-placeholder.shimmer .monaco-icon-label-container { height: 18px; - background: var(--vscode-scm-historyItemDefaultLabelBackground); + background: var(--vscode-scmGraph-historyItemHoverDefaultLabelBackground); border-radius: 2px; opacity: 0.5; } diff --git a/src/vs/workbench/contrib/scm/browser/scmHistory.ts b/src/vs/workbench/contrib/scm/browser/scmHistory.ts index c51e6fbcdd8..08e24fe1870 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistory.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistory.ts @@ -43,11 +43,11 @@ export const colorRegistry: ColorIdentifier[] = [ registerColor('scmGraph.foreground3', chartsYellow, localize('scmGraphForeground3', "Source control graph foreground color (3).")), ]; -function getLabelColorIdentifier(historyItem: ISCMHistoryItem, colorMap: Map): ColorIdentifier | undefined { - for (const label of historyItem.labels ?? []) { - const colorIndex = colorMap.get(label.title); - if (colorIndex !== undefined) { - return colorIndex; +function getLabelColorIdentifier(historyItem: ISCMHistoryItem, colorMap: Map): ColorIdentifier | undefined { + for (const ref of historyItem.references ?? []) { + const colorIdentifier = colorMap.get(ref.id); + if (colorIdentifier !== undefined) { + return colorIdentifier; } } @@ -215,7 +215,7 @@ export function renderSCMHistoryItemGraph(historyItemViewModel: ISCMHistoryItemV } else { // HEAD // TODO@lszomoru - implement a better way to determine if the commit is HEAD - if (historyItem.labels?.some(l => ThemeIcon.isThemeIcon(l.icon) && l.icon.id === 'target')) { + if (historyItem.references?.some(ref => ThemeIcon.isThemeIcon(ref.icon) && ref.icon.id === 'target')) { const outerCircle = drawCircle(circleIndex, CIRCLE_RADIUS + 2, circleColor); svg.append(outerCircle); } @@ -246,7 +246,7 @@ export function renderSCMHistoryGraphPlaceholder(columns: ISCMHistoryItemGraphNo return elements.root; } -export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[], colorMap = new Map()): ISCMHistoryItemViewModel[] { +export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[], colorMap = new Map()): ISCMHistoryItemViewModel[] { let colorIndex = -1; const viewModels: ISCMHistoryItemViewModel[] = []; @@ -302,11 +302,11 @@ export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[], }); } - // Add colors to labels - const labels = (historyItem.labels ?? []) - .map(label => { - let color = colorMap.get(label.title); - if (!color && colorMap.has('*')) { + // Add colors to references + const references = (historyItem.references ?? []) + .map(ref => { + let color = colorMap.get(ref.id); + if (colorMap.has(ref.id) && color === undefined) { // Find the history item in the input swimlanes const inputIndex = inputSwimlanes.findIndex(node => node.id === historyItem.id); @@ -318,13 +318,13 @@ export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[], circleIndex < inputSwimlanes.length ? inputSwimlanes[circleIndex].color : historyItemGroupLocal; } - return { ...label, color }; + return { ...ref, color }; }); viewModels.push({ historyItem: { ...historyItem, - labels + references }, inputSwimlanes, outputSwimlanes, diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index 51916fe9657..00660ae54a1 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -34,7 +34,7 @@ import { IViewPaneOptions, ViewAction, ViewPane, ViewPaneShowActions } from '../ import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; import { renderSCMHistoryItemGraph, historyItemGroupLocal, historyItemGroupRemote, historyItemGroupBase, toISCMHistoryItemViewModelArray, SWIMLANE_WIDTH, renderSCMHistoryGraphPlaceholder, historyItemHoverDeletionsForeground, historyItemHoverLabelForeground, historyItemHoverAdditionsForeground, historyItemHoverDefaultLabelForeground, historyItemHoverDefaultLabelBackground } from './scmHistory.js'; import { isSCMHistoryItemLoadMoreTreeElement, isSCMHistoryItemViewModelTreeElement, isSCMRepository } from './util.js'; -import { ISCMHistoryItem, ISCMHistoryItemGroup, ISCMHistoryItemViewModel, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; +import { ISCMHistoryItem, ISCMHistoryItemRef, ISCMHistoryItemViewModel, ISCMHistoryProvider, SCMHistoryItemLoadMoreTreeElement, SCMHistoryItemViewModelTreeElement } from '../common/history.js'; import { HISTORY_VIEW_PANE_ID, ISCMProvider, ISCMRepository, ISCMService, ISCMViewService } from '../common/scm.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { stripIcons } from '../../../../base/common/iconLabels.js'; @@ -45,10 +45,10 @@ import { Sequencer, Throttler } from '../../../../base/common/async.js'; import { URI } from '../../../../base/common/uri.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { ActionRunner, IAction, IActionRunner } from '../../../../base/common/actions.js'; -import { tail } from '../../../../base/common/arrays.js'; +import { delta, groupBy, tail } from '../../../../base/common/arrays.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { IProgressService } from '../../../../platform/progress/common/progress.js'; -import { constObservable, derivedConstOnceDefined, latestChangedValue, observableFromEvent } from '../../../../base/common/observableInternal/utils.js'; +import { constObservable, derivedConstOnceDefined, latestChangedValue, observableFromEvent, runOnChange } from '../../../../base/common/observableInternal/utils.js'; import { ContextKeys } from './scmViewPane.js'; import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IDropdownMenuActionViewItemOptions } from '../../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; @@ -60,6 +60,7 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { clamp } from '../../../../base/common/numbers.js'; import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { structuralEquals } from '../../../../base/common/equals.js'; +import { compare } from '../../../../base/common/strings.js'; type TreeElement = SCMHistoryItemViewModelTreeElement | SCMHistoryItemLoadMoreTreeElement; @@ -76,10 +77,31 @@ class SCMRepositoryActionViewItem extends ActionViewItem { } } +class SCMHistoryItemRefsActionViewItem extends ActionViewItem { + constructor(private readonly _historyItemsFilter: HistoryItemRefsFilter, action: IAction, options?: IDropdownMenuActionViewItemOptions) { + super(null, action, { ...options, icon: false, label: true }); + } + + protected override updateLabel(): void { + if (this.options.label && this.label) { + this.label.classList.add('scm-graph-history-item-picker'); + if (this._historyItemsFilter === 'all') { + reset(this.label, ...renderLabelWithIcons(`$(git-branch) ${localize('all', "All")}`)); + } else if (this._historyItemsFilter === 'auto') { + reset(this.label, ...renderLabelWithIcons(`$(git-branch) ${localize('auto', "Auto")}`)); + } else if (this._historyItemsFilter.length === 1) { + reset(this.label, ...renderLabelWithIcons(`$(git-branch) ${this._historyItemsFilter[0].name}`)); + } else { + reset(this.label, ...renderLabelWithIcons(`$(git-branch) ${this._historyItemsFilter.length} ${localize('items', "Items")}`)); + } + } + } +} + registerAction2(class extends ViewAction { constructor() { super({ - id: 'workbench.scm.action.repository', + id: 'workbench.scm.graph.action.pickRepository', title: '', viewId: HISTORY_VIEW_PANE_ID, f1: false, @@ -97,6 +119,28 @@ registerAction2(class extends ViewAction { } }); +registerAction2(class extends ViewAction { + constructor() { + super({ + id: 'workbench.scm.graph.action.pickHistoryItemRefs', + title: '', + icon: Codicon.gitBranch, + viewId: HISTORY_VIEW_PANE_ID, + f1: false, + menu: { + id: MenuId.SCMHistoryTitle, + group: 'navigation', + order: 0 + } + }); + } + + async runInView(_: ServicesAccessor, view: SCMHistoryViewPane): Promise { + view.pickHistoryItemRef(); + } +}); + + registerAction2(class extends ViewAction { constructor() { super({ @@ -241,36 +285,36 @@ class HistoryItemRenderer implements ITreeRenderer { const labelConfig = this._badgesConfig.read(reader); templateData.labelContainer.textContent = ''; - const firstColoredLabel = historyItem.labels?.find(label => label.color); + const firstColoredRef = historyItem.references?.find(ref => ref.color); - for (const label of historyItem.labels ?? []) { - if (!label.color && labelConfig === 'filter') { + for (const ref of historyItem.references ?? []) { + if (!ref.color && labelConfig === 'filter') { continue; } - if (label.icon && ThemeIcon.isThemeIcon(label.icon)) { + if (ref.icon && ThemeIcon.isThemeIcon(ref.icon)) { const elements = h('div.label', { style: { - color: label.color ? asCssVariable(historyItemHoverLabelForeground) : asCssVariable(foreground), - backgroundColor: label.color ? asCssVariable(label.color) : asCssVariable(historyItemHoverDefaultLabelBackground) + color: ref.color ? asCssVariable(historyItemHoverLabelForeground) : asCssVariable(foreground), + backgroundColor: ref.color ? asCssVariable(ref.color) : asCssVariable(historyItemHoverDefaultLabelBackground) } }, [ h('div.icon@icon'), h('div.description@description') ]); - elements.icon.classList.add(...ThemeIcon.asClassNameArray(label.icon)); + elements.icon.classList.add(...ThemeIcon.asClassNameArray(ref.icon)); - elements.description.textContent = label.title; - elements.description.style.display = label === firstColoredLabel ? '' : 'none'; + elements.description.textContent = ref.name; + elements.description.style.display = ref === firstColoredRef ? '' : 'none'; append(templateData.labelContainer, elements.root); } @@ -320,15 +364,15 @@ class HistoryItemRenderer implements ITreeRenderer 0) { + if ((historyItem.references ?? []).length > 0) { markdown.appendMarkdown(`\n\n---\n\n`); - markdown.appendMarkdown((historyItem.labels ?? []).map(label => { - const labelIconId = ThemeIcon.isThemeIcon(label.icon) ? label.icon.id : ''; + markdown.appendMarkdown((historyItem.references ?? []).map(ref => { + const labelIconId = ThemeIcon.isThemeIcon(ref.icon) ? ref.icon.id : ''; - const labelBackgroundColor = label.color ? asCssVariable(label.color) : asCssVariable(historyItemHoverDefaultLabelBackground); - const labelForegroundColor = label.color ? asCssVariable(historyItemHoverLabelForeground) : asCssVariable(historyItemHoverDefaultLabelForeground); + const labelBackgroundColor = ref.color ? asCssVariable(ref.color) : asCssVariable(historyItemHoverDefaultLabelBackground); + const labelForegroundColor = ref.color ? asCssVariable(historyItemHoverLabelForeground) : asCssVariable(historyItemHoverDefaultLabelForeground); - return ` $(${labelIconId}) ${label.title} `; + return ` $(${labelIconId}) ${ref.name} `; }).join('  ')); } @@ -510,8 +554,6 @@ class SCMHistoryTreeKeyboardNavigationLabelProvider implements IKeyboardNavigati } } -type HistoryItemState = { currentHistoryItemGroup: ISCMHistoryItemGroup; items: ISCMHistoryItem[]; loadMore: boolean }; - class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource { async getChildren(inputOrElement: SCMHistoryViewModel | TreeElement): Promise> { @@ -543,6 +585,9 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource(this, 'auto'); - - readonly historyItemGroupFilter = derived(reader => { - const filter = this._historyItemGroupFilter.read(reader); - if (Array.isArray(filter)) { - return filter; - } - - if (filter === 'all') { - return []; - } - - const repository = this.repository.get(); - const historyProvider = repository?.provider.historyProvider.get(); - const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup.get(); - - if (!currentHistoryItemGroup) { - return []; - } - - return [ - currentHistoryItemGroup.revision ?? currentHistoryItemGroup.id, - ...currentHistoryItemGroup.remote ? [currentHistoryItemGroup.remote.revision ?? currentHistoryItemGroup.remote.id] : [], - ...currentHistoryItemGroup.base ? [currentHistoryItemGroup.base.revision ?? currentHistoryItemGroup.base.id] : [], - ]; - }); + readonly historyItemsFilter = observableValue(this, 'auto'); private readonly _state = new Map(); @@ -652,24 +671,44 @@ class SCMHistoryViewModel extends Disposable { let state = this._state.get(repository); const historyProvider = repository.provider.historyProvider.get(); - const currentHistoryItemGroup = state?.currentHistoryItemGroup ?? historyProvider?.currentHistoryItemGroup.get(); - if (!historyProvider || !currentHistoryItemGroup) { + if (!historyProvider) { return []; } if (!state || state.loadMore) { const existingHistoryItems = state?.items ?? []; + let historyItemRefs = state?.historyItemRefs; + + if (!historyItemRefs) { + const historyItemsFilter = this.historyItemsFilter.get(); + + switch (historyItemsFilter) { + case 'all': + historyItemRefs = await historyProvider.provideHistoryItemRefs() ?? []; + break; + case 'auto': + historyItemRefs = [ + historyProvider.currentHistoryItemRef.get(), + historyProvider.currentHistoryItemRemoteRef.get(), + historyProvider.currentHistoryItemBaseRef.get(), + ].filter(ref => !!ref); + break; + default: + historyItemRefs = historyItemsFilter; + break; + } + } - const historyItemGroupIds = this.historyItemGroupFilter.get(); const limit = clamp(this._configurationService.getValue('scm.graph.pageSize'), 1, 1000); + const historyItemGroupIds = historyItemRefs.map(ref => ref.revision ?? ref.id); const historyItems = await historyProvider.provideHistoryItems({ historyItemGroupIds, limit, skip: existingHistoryItems.length }) ?? []; state = { - currentHistoryItemGroup, + historyItemRefs, items: [...existingHistoryItems, ...historyItems], loadMore: false }; @@ -678,7 +717,7 @@ class SCMHistoryViewModel extends Disposable { } // Create the color map - const colorMap = this._getGraphColorMap(currentHistoryItemGroup); + const colorMap = this._getGraphColorMap(state.historyItemRefs); return toISCMHistoryItemViewModelArray(state.items, colorMap) .map(historyItemViewModel => ({ @@ -692,18 +731,34 @@ class SCMHistoryViewModel extends Disposable { this._selectedRepository.set(repository, undefined); } - private _getGraphColorMap(currentHistoryItemGroup: ISCMHistoryItemGroup): Map { - const colorMap = new Map([ - [currentHistoryItemGroup.name, historyItemGroupLocal] - ]); - if (currentHistoryItemGroup.remote) { - colorMap.set(currentHistoryItemGroup.remote.name, historyItemGroupRemote); + setHistoryItemsFilter(filter: 'all' | 'auto' | ISCMHistoryItemRef[]): void { + this.historyItemsFilter.set(filter, undefined); + } + + private _getGraphColorMap(historyItemRefs: ISCMHistoryItemRef[]): Map { + const repository = this.repository.get(); + const historyProvider = repository?.provider.historyProvider.get(); + const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup.get(); + + const colorMap = new Map(); + if (currentHistoryItemGroup) { + colorMap.set(currentHistoryItemGroup.id, historyItemGroupLocal); + if (currentHistoryItemGroup.remote) { + colorMap.set(currentHistoryItemGroup.remote.id, historyItemGroupRemote); + } + if (currentHistoryItemGroup.base) { + colorMap.set(currentHistoryItemGroup.base.id, historyItemGroupBase); + } } - if (currentHistoryItemGroup.base) { - colorMap.set(currentHistoryItemGroup.base.name, historyItemGroupBase); - } - if (this._historyItemGroupFilter.get() === 'all') { - colorMap.set('*', ''); + + // Add the remaining history item references to the color map + // if not already present. These history item references will + // be colored using the color of the history item to which they + // point to. + for (const ref of historyItemRefs) { + if (!colorMap.has(ref.id)) { + colorMap.set(ref.id, undefined); + } } return colorMap; @@ -715,6 +770,166 @@ class SCMHistoryViewModel extends Disposable { } } +type RepositoryQuickPickItem = IQuickPickItem & { repository: 'auto' | ISCMRepository }; + +class RepositoryPicker extends Disposable { + private readonly _autoQuickPickItem: RepositoryQuickPickItem = { + label: localize('auto', "Auto"), + description: localize('activeRepository', "Show the source control graph for the active repository"), + repository: 'auto' + }; + + constructor( + private readonly _scmViewService: ISCMViewService, + private readonly _quickInputService: IQuickInputService, + ) { + super(); + } + + async pickRepository(): Promise { + const picks: (RepositoryQuickPickItem | IQuickPickSeparator)[] = [ + this._autoQuickPickItem, + { type: 'separator' }]; + + picks.push(...this._scmViewService.repositories.map(r => ({ + label: r.provider.name, + description: r.provider.rootUri?.fsPath, + iconClass: ThemeIcon.asClassName(Codicon.repo), + repository: r + }))); + + return this._quickInputService.pick(picks, { + placeHolder: localize('scmGraphRepository', "Select the repository to view, type to filter all repositories") + }); + } +} + +type HistoryItemRefQuickPickItem = IQuickPickItem & { historyItemRef: 'all' | 'auto' | ISCMHistoryItemRef }; + +class HistoryItemRefPicker extends Disposable { + private readonly _allQuickPickItem: HistoryItemRefQuickPickItem = { + id: 'all', + label: localize('all', "All"), + description: localize('allHistoryItemRefs', "Show all history item references"), + historyItemRef: 'all' + }; + + private readonly _autoQuickPickItem: HistoryItemRefQuickPickItem = { + id: 'auto', + label: localize('auto', "Auto"), + description: localize('currentHistoryItemRef', "Show the current history item reference"), + historyItemRef: 'auto' + }; + + constructor( + private readonly _historyProvider: ISCMHistoryProvider, + private readonly _historyItemsFilter: 'all' | 'auto' | ISCMHistoryItemRef[], + @IQuickInputService private readonly _quickInputService: IQuickInputService, + ) { + super(); + } + + async pickHistoryItemRef(): Promise<'all' | 'auto' | ISCMHistoryItemRef[] | undefined> { + const quickPick = this._quickInputService.createQuickPick({ useSeparators: true }); + this._store.add(quickPick); + + quickPick.placeholder = localize('scmGraphHistoryItemRef', "Select one/more history item references to view, type to filter"); + quickPick.canSelectMany = true; + quickPick.hideCheckAll = true; + quickPick.busy = true; + quickPick.show(); + + quickPick.items = await this._createQuickPickItems(); + quickPick.busy = false; + + // Set initial selection + let selectedItems: HistoryItemRefQuickPickItem[] = []; + if (this._historyItemsFilter === 'all') { + selectedItems.push(this._allQuickPickItem); + quickPick.selectedItems = [this._allQuickPickItem]; + } else if (this._historyItemsFilter === 'auto') { + selectedItems.push(this._autoQuickPickItem); + quickPick.selectedItems = [this._autoQuickPickItem]; + } else { + for (const item of quickPick.items) { + if (item.type === 'separator') { + continue; + } + + if (this._historyItemsFilter.some(ref => ref.id === item.id)) { + selectedItems.push(item); + } + } + + quickPick.selectedItems = selectedItems; + } + + return new Promise<'all' | 'auto' | ISCMHistoryItemRef[] | undefined>(resolve => { + this._store.add(quickPick.onDidChangeSelection(items => { + const { added } = delta(selectedItems, items, (a, b) => compare(a.id ?? '', b.id ?? '')); + if (added.length > 0) { + if (added[0].historyItemRef === 'all' || added[0].historyItemRef === 'auto') { + quickPick.selectedItems = [added[0]]; + } else { + // Remove 'all' and 'auto' items if present + quickPick.selectedItems = [...quickPick.selectedItems + .filter(i => i.historyItemRef !== 'all' && i.historyItemRef !== 'auto')]; + } + } + + selectedItems = [...quickPick.selectedItems]; + })); + + this._store.add(quickPick.onDidAccept(() => { + if (selectedItems.length === 1 && selectedItems[0].historyItemRef === 'all') { + resolve('all'); + } else if (selectedItems.length === 1 && selectedItems[0].historyItemRef === 'auto') { + resolve('auto'); + } else { + resolve(selectedItems.map(item => item.historyItemRef) as ISCMHistoryItemRef[]); + } + + quickPick.hide(); + })); + + this._store.add(quickPick.onDidHide(() => { + resolve(undefined); + this.dispose(); + })); + }); + } + + private async _createQuickPickItems(): Promise<(HistoryItemRefQuickPickItem | IQuickPickSeparator)[]> { + const picks: (HistoryItemRefQuickPickItem | IQuickPickSeparator)[] = [ + this._allQuickPickItem, this._autoQuickPickItem + ]; + + const historyItemRefs = await this._historyProvider.provideHistoryItemRefs() ?? []; + const historyItemRefsByCategory = groupBy(historyItemRefs, (a, b) => compare(a.category ?? '', b.category ?? '')); + + for (const refs of historyItemRefsByCategory) { + if (refs.length === 0) { + continue; + } + + picks.push({ type: 'separator', label: refs[0].category }); + + picks.push(...refs.map(ref => { + return { + id: ref.id, + label: ref.name, + description: ref.description, + iconClass: ThemeIcon.isThemeIcon(ref.icon) ? + ThemeIcon.asClassName(ref.icon) : undefined, + historyItemRef: ref + }; + })); + } + + return picks; + } +} + export class SCMHistoryViewPane extends ViewPane { private _treeContainer!: HTMLElement; @@ -736,9 +951,8 @@ export class SCMHistoryViewPane extends ViewPane { constructor( options: IViewPaneOptions, @ICommandService private readonly _commandService: ICommandService, - @ISCMViewService private readonly _scmViewService: ISCMViewService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IProgressService private readonly _progressService: IProgressService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService keybindingService: IKeybindingService, @@ -871,6 +1085,11 @@ export class SCMHistoryViewPane extends ViewPane { } })); + // HistoryItemRefs filter changed + store.add(runOnChange(this._treeViewModel.historyItemsFilter, () => { + this.refresh(); + })); + if (changeSummary.refresh) { this.refresh(); } @@ -891,11 +1110,16 @@ export class SCMHistoryViewPane extends ViewPane { } override getActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined { - if (action.id === 'workbench.scm.action.repository') { + if (action.id === 'workbench.scm.graph.action.pickRepository') { const repository = this._treeViewModel?.repository.get(); if (repository) { return new SCMRepositoryActionViewItem(repository, action, options); } + } else if (action.id === 'workbench.scm.graph.action.pickHistoryItemRefs') { + const historyItemsFilter = this._treeViewModel?.historyItemsFilter.get(); + if (historyItemsFilter) { + return new SCMHistoryItemRefsActionViewItem(historyItemsFilter, action, options); + } } return super.getActionViewItem(action, options); @@ -910,33 +1134,31 @@ export class SCMHistoryViewPane extends ViewPane { } async pickRepository(): Promise { - const picks: (IQuickPickItem & { repository: 'auto' | ISCMRepository } | IQuickPickSeparator)[] = [ - { - label: localize('auto', "Auto"), - description: localize('activeRepository', "Show the source control graph for the active repository"), - repository: 'auto' - }, - { - type: 'separator' - }, - ]; - - picks.push(...this._scmViewService.repositories.map(r => ({ - label: r.provider.name, - description: r.provider.rootUri?.fsPath, - iconClass: ThemeIcon.asClassName(Codicon.repo), - repository: r - }))); - - const result = await this._quickInputService.pick(picks, { - placeHolder: localize('scmGraphRepository', "Select the repository to view, type to filter all repositories") - }); + const picker = this._instantiationService.createInstance(RepositoryPicker); + const result = await picker.pickRepository(); if (result) { this._treeViewModel.setRepository(result.repository); } } + async pickHistoryItemRef(): Promise { + const repository = this._treeViewModel.repository.get(); + const historyProvider = repository?.provider.historyProvider.get(); + const historyItemsFilter = this._treeViewModel.historyItemsFilter.get(); + + if (!historyProvider) { + return; + } + + const picker = this._instantiationService.createInstance(HistoryItemRefPicker, historyProvider, historyItemsFilter); + const result = await picker.pickHistoryItemRef(); + + if (result) { + this._treeViewModel.setHistoryItemsFilter(result); + } + } + private _createTree(container: HTMLElement): void { this._treeIdentityProvider = new SCMHistoryTreeIdentityProvider(); diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index f5d4de3fa52..dd61d7362e4 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -17,6 +17,11 @@ export interface ISCMHistoryProviderMenus { export interface ISCMHistoryProvider { readonly currentHistoryItemGroup: IObservable; + readonly currentHistoryItemRef: IObservable; + readonly currentHistoryItemRemoteRef: IObservable; + readonly currentHistoryItemBaseRef: IObservable; + + provideHistoryItemRefs(): Promise; provideHistoryItems(options: ISCMHistoryOptions): Promise; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise; resolveHistoryItemGroupCommonAncestor(historyItemGroupIds: string[]): Promise; @@ -43,10 +48,14 @@ export interface ISCMHistoryItemStatistics { readonly deletions: number; } -export interface ISCMHistoryItemLabel { - readonly title: string; - readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; +export interface ISCMHistoryItemRef { + readonly id: string; + readonly name: string; + readonly revision?: string; + readonly category?: string; + readonly description?: string; readonly color?: ColorIdentifier; + readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; } export interface ISCMHistoryItem { @@ -58,7 +67,7 @@ export interface ISCMHistoryItem { readonly author?: string; readonly timestamp?: number; readonly statistics?: ISCMHistoryItemStatistics; - readonly labels?: ISCMHistoryItemLabel[]; + readonly references?: ISCMHistoryItemRef[]; } export interface ISCMHistoryItemGraphNode { diff --git a/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts b/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts index 9da7394ecd7..0cb64cfdda0 100644 --- a/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts +++ b/src/vs/workbench/contrib/scm/test/browser/scmHistory.test.ts @@ -7,10 +7,10 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { ColorIdentifier } from '../../../../../platform/theme/common/colorUtils.js'; import { colorRegistry, historyItemGroupBase, historyItemGroupLocal, historyItemGroupRemote, toISCMHistoryItemViewModelArray } from '../../browser/scmHistory.js'; -import { ISCMHistoryItem, ISCMHistoryItemLabel } from '../../common/history.js'; +import { ISCMHistoryItem, ISCMHistoryItemRef } from '../../common/history.js'; -function toSCMHistoryItem(id: string, parentIds: string[], labels?: ISCMHistoryItemLabel[]): ISCMHistoryItem { - return { id, parentIds, subject: '', message: '', labels } satisfies ISCMHistoryItem; +function toSCMHistoryItem(id: string, parentIds: string[], references?: ISCMHistoryItemRef[]): ISCMHistoryItem { + return { id, parentIds, subject: '', message: '', references } satisfies ISCMHistoryItem; } suite('toISCMHistoryItemViewModelArray', () => { @@ -517,12 +517,12 @@ suite('toISCMHistoryItemViewModelArray', () => { */ test('graph with color map', () => { const models = [ - toSCMHistoryItem('a', ['b'], [{ title: 'topic' }]), + toSCMHistoryItem('a', ['b'], [{ id: 'topic', name: 'topic' }]), toSCMHistoryItem('b', ['c']), - toSCMHistoryItem('c', ['d'], [{ title: 'origin/topic' }]), + toSCMHistoryItem('c', ['d'], [{ id: 'origin/topic', name: 'origin/topic' }]), toSCMHistoryItem('d', ['e']), toSCMHistoryItem('e', ['f', 'g']), - toSCMHistoryItem('g', ['h'], [{ title: 'origin/main' }]) + toSCMHistoryItem('g', ['h'], [{ id: 'origin/main', name: 'origin/main' }]) ]; const colorMap = new Map([ diff --git a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts index 39ee5f8281e..fcd0286936f 100644 --- a/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts @@ -20,10 +20,11 @@ declare module 'vscode' { onDidChangeCurrentHistoryItemGroup: Event; /** - * Fires when the history item groups change (ex: commit, push, fetch) + * Fires when history item refs change */ - // onDidChangeHistoryItemGroups: Event; + onDidChangeHistory: Event; + provideHistoryItemRefs(token: CancellationToken): ProviderResult; provideHistoryItems(options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult; provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult; @@ -51,11 +52,6 @@ declare module 'vscode' { readonly deletions: number; } - export interface SourceControlHistoryItemLabel { - readonly title: string; - readonly icon?: Uri | { light: Uri; dark: Uri } | ThemeIcon; - } - export interface SourceControlHistoryItem { readonly id: string; readonly parentIds: string[]; @@ -64,7 +60,16 @@ declare module 'vscode' { readonly author?: string; readonly timestamp?: number; readonly statistics?: SourceControlHistoryItemStatistics; - readonly labels?: SourceControlHistoryItemLabel[]; + readonly references?: SourceControlHistoryItemRef[]; + } + + export interface SourceControlHistoryItemRef { + readonly id: string; + readonly name: string; + readonly description?: string; + readonly revision?: string; + readonly category?: string; + readonly icon?: Uri | { light: Uri; dark: Uri } | ThemeIcon; } export interface SourceControlHistoryItemChange { @@ -74,10 +79,9 @@ declare module 'vscode' { readonly renameUri: Uri | undefined; } - // export interface SourceControlHistoryChangeEvent { - // readonly added: Iterable; - // readonly removed: Iterable; - // readonly modified: Iterable; - // } - + export interface SourceControlHistoryChangeEvent { + readonly added: Iterable; + readonly removed: Iterable; + readonly modified: Iterable; + } }