SCM Graph - add branch picker (#227949)

* WIP - saving my work

* Extract HistoryItemRef picker

* Extract Repository picker

* Improve history item ref picker rendering

* Refactor color map

* Refresh the graph when the filter changes

* Push minor fix
This commit is contained in:
Ladislau Szomoru
2024-09-09 12:02:31 +02:00
committed by GitHub
parent 884cfb16a3
commit 3ab41c2f69
10 changed files with 534 additions and 161 deletions
+87 -17
View File
@@ -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<void>();
readonly onDidChangeCurrentHistoryItemGroup: Event<void> = this._onDidChangeCurrentHistoryItemGroup.event;
private readonly _onDidChangeHistory = new EventEmitter<SourceControlHistoryChangeEvent>();
readonly onDidChangeHistory: Event<SourceControlHistoryChangeEvent> = this._onDidChangeHistory.event;
private readonly _onDidChangeDecorations = new EventEmitter<Uri[]>();
readonly onDidChangeFileDecorations: Event<Uri[]> = this._onDidChangeDecorations.event;
@@ -28,12 +31,6 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
}
private historyItemDecorations = new Map<string, FileDecoration>();
private historyItemLabels = new Map<string, ThemeIcon>([
['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<SourceControlHistoryItemRef[]> {
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<SourceControlHistoryItem[]> {
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<Branch | undefined> {
+57 -5
View File
@@ -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<ISCMHistoryItemRef | undefined>({
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<ISCMHistoryItemRef | undefined>({
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<ISCMHistoryItemRef | undefined>({
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<string | undefined> {
return this.proxy.$resolveHistoryItemGroupCommonAncestor(this.handle, historyItemGroupIds, CancellationToken.None);
}
async provideHistoryItemRefs(): Promise<ISCMHistoryItemRef[] | undefined> {
const historyItemRefs = await this.proxy.$provideHistoryItemRefs(this.handle, CancellationToken.None);
return historyItemRefs?.map(ref => ({ ...ref, icon: getIconFromIconDto(ref.icon) }));
}
async provideHistoryItems(options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined> {
const historyItems = await this.proxy.$provideHistoryItems(this.handle, options, CancellationToken.None);
return historyItems?.map(historyItem => toISCMHistoryItem(historyItem));
@@ -1550,6 +1550,15 @@ export interface SCMHistoryItemGroupDto {
readonly remote?: Omit<Omit<SCMHistoryItemGroupDto, 'base'>, '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<void>;
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>;
$setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise<void>;
$provideHistoryItemRefs(sourceControlHandle: number, token: CancellationToken): Promise<SCMHistoryItemRefDto[] | undefined>;
$provideHistoryItems(sourceControlHandle: number, options: any, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined>;
$provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemChangeDto[] | undefined>;
$resolveHistoryItemGroupCommonAncestor(sourceControlHandle: number, historyItemGroupIds: string[], token: CancellationToken): Promise<string | undefined>;
+11 -4
View File
@@ -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<SCMHistoryItemRefDto[] | undefined> {
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<SCMHistoryItemDto[] | undefined> {
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
const historyItems = await historyProvider?.provideHistoryItems(options, token);
@@ -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;
}
@@ -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<string, ColorIdentifier>): 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<string, ColorIdentifier | undefined>): 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<string, string>()): ISCMHistoryItemViewModel[] {
export function toISCMHistoryItemViewModelArray(historyItems: ISCMHistoryItem[], colorMap = new Map<string, ColorIdentifier | undefined>()): 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,
@@ -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<SCMHistoryViewPane> {
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<SCMHistoryViewPane> {
}
});
registerAction2(class extends ViewAction<SCMHistoryViewPane> {
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<void> {
view.pickHistoryItemRef();
}
});
registerAction2(class extends ViewAction<SCMHistoryViewPane> {
constructor() {
super({
@@ -241,36 +285,36 @@ class HistoryItemRenderer implements ITreeRenderer<SCMHistoryItemViewModelTreeEl
const [matches, descriptionMatches] = this.processMatches(historyItemViewModel, node.filterData);
templateData.label.setLabel(historyItem.subject, historyItem.author, { matches, descriptionMatches, extraClasses });
this._renderLabels(historyItem, templateData);
this._renderBadges(historyItem, templateData);
}
private _renderLabels(historyItem: ISCMHistoryItem, templateData: HistoryItemTemplate): void {
private _renderBadges(historyItem: ISCMHistoryItem, templateData: HistoryItemTemplate): void {
templateData.elementDisposables.add(autorun(reader => {
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<SCMHistoryItemViewModelTreeEl
}
}
if ((historyItem.labels ?? []).length > 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 `<span style="color:${labelForegroundColor};background-color:${labelBackgroundColor};border-radius:2px;">&nbsp;$(${labelIconId})&nbsp;${label.title}&nbsp;</span>`;
return `<span style="color:${labelForegroundColor};background-color:${labelBackgroundColor};border-radius:2px;">&nbsp;$(${labelIconId})&nbsp;${ref.name}&nbsp;</span>`;
}).join('&nbsp;&nbsp;'));
}
@@ -510,8 +554,6 @@ class SCMHistoryTreeKeyboardNavigationLabelProvider implements IKeyboardNavigati
}
}
type HistoryItemState = { currentHistoryItemGroup: ISCMHistoryItemGroup; items: ISCMHistoryItem[]; loadMore: boolean };
class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource<SCMHistoryViewModel, TreeElement> {
async getChildren(inputOrElement: SCMHistoryViewModel | TreeElement): Promise<Iterable<TreeElement>> {
@@ -543,6 +585,9 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource<SC
}
}
type HistoryItemRefsFilter = 'all' | 'auto' | ISCMHistoryItemRef[];
type HistoryItemState = { historyItemRefs: ISCMHistoryItemRef[]; items: ISCMHistoryItem[]; loadMore: boolean };
class SCMHistoryViewModel extends Disposable {
private readonly _closedRepository = observableFromEvent(
@@ -574,33 +619,7 @@ class SCMHistoryViewModel extends Disposable {
* values are updated in the same transaction (or during the initial read of the observable value).
*/
readonly repository = latestChangedValue(this, [this._firstRepository, this._graphRepository]);
private readonly _historyItemGroupFilter = observableValue<'all' | 'auto' | string[]>(this, 'auto');
readonly historyItemGroupFilter = derived<string[]>(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<HistoryItemRefsFilter>(this, 'auto');
private readonly _state = new Map<ISCMRepository, HistoryItemState>();
@@ -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<number>('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<string, ColorIdentifier> {
const colorMap = new Map<string, ColorIdentifier>([
[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<string, ColorIdentifier | undefined> {
const repository = this.repository.get();
const historyProvider = repository?.provider.historyProvider.get();
const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup.get();
const colorMap = new Map<string, ColorIdentifier | undefined>();
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<RepositoryQuickPickItem | undefined> {
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<HistoryItemRefQuickPickItem>({ 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<void> {
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<void> {
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();
+13 -4
View File
@@ -17,6 +17,11 @@ export interface ISCMHistoryProviderMenus {
export interface ISCMHistoryProvider {
readonly currentHistoryItemGroup: IObservable<ISCMHistoryItemGroup | undefined>;
readonly currentHistoryItemRef: IObservable<ISCMHistoryItemRef | undefined>;
readonly currentHistoryItemRemoteRef: IObservable<ISCMHistoryItemRef | undefined>;
readonly currentHistoryItemBaseRef: IObservable<ISCMHistoryItemRef | undefined>;
provideHistoryItemRefs(): Promise<ISCMHistoryItemRef[] | undefined>;
provideHistoryItems(options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined>;
provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise<ISCMHistoryItemChange[] | undefined>;
resolveHistoryItemGroupCommonAncestor(historyItemGroupIds: string[]): Promise<string | undefined>;
@@ -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 {
@@ -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<string, ColorIdentifier>([
+18 -14
View File
@@ -20,10 +20,11 @@ declare module 'vscode' {
onDidChangeCurrentHistoryItemGroup: Event<void>;
/**
* Fires when the history item groups change (ex: commit, push, fetch)
* Fires when history item refs change
*/
// onDidChangeHistoryItemGroups: Event<SourceControlHistoryChangeEvent>;
onDidChangeHistory: Event<SourceControlHistoryChangeEvent>;
provideHistoryItemRefs(token: CancellationToken): ProviderResult<SourceControlHistoryItemRef[]>;
provideHistoryItems(options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult<SourceControlHistoryItem[]>;
provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult<SourceControlHistoryItemChange[]>;
@@ -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<SourceControlHistoryItemGroup>;
// readonly removed: Iterable<SourceControlHistoryItemGroup>;
// readonly modified: Iterable<SourceControlHistoryItemGroup>;
// }
export interface SourceControlHistoryChangeEvent {
readonly added: Iterable<SourceControlHistoryItemRef>;
readonly removed: Iterable<SourceControlHistoryItemRef>;
readonly modified: Iterable<SourceControlHistoryItemRef>;
}
}