Skip to content

Commit e7be6e9

Browse files
authored
SCM - add proposed API to specify source control provider icons (microsoft#256762)
1 parent df5b80f commit e7be6e9

File tree

9 files changed

+51
-41
lines changed

9 files changed

+51
-41
lines changed

extensions/git/src/repository.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import TelemetryReporter from '@vscode/extension-telemetry';
77
import * as fs from 'fs';
88
import * as path from 'path';
99
import picomatch from 'picomatch';
10-
import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, Uri, window, workspace, WorkspaceEdit } from 'vscode';
10+
import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
1111
import { ActionButton } from './actionButton';
1212
import { ApiRepository } from './api/api1';
1313
import { Branch, BranchQuery, Change, CommitOptions, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git';
@@ -899,8 +899,15 @@ export class Repository implements Disposable {
899899
: undefined;
900900
const parent = this.repositoryResolver.getRepository(parentRoot)?.sourceControl;
901901

902+
// Icon
903+
const icon = repository.kind === 'submodule'
904+
? new ThemeIcon('archive')
905+
: repository.kind === 'worktree'
906+
? new ThemeIcon('list-tree')
907+
: new ThemeIcon('repo');
908+
902909
const root = Uri.file(repository.root);
903-
this._sourceControl = scm.createSourceControl('git', 'Git', root, parent);
910+
this._sourceControl = scm.createSourceControl('git', 'Git', root, icon, parent);
904911
this._sourceControl.contextValue = repository.kind;
905912

906913
this._sourceControl.quickDiffProvider = this;

src/vs/workbench/api/browser/mainThreadSCM.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ class MainThreadSCMProvider implements ISCMProvider {
274274
get handle(): number { return this._handle; }
275275
get label(): string { return this._label; }
276276
get rootUri(): URI | undefined { return this._rootUri; }
277+
get iconPath(): URI | { light: URI; dark: URI } | ThemeIcon | undefined { return this._iconPath; }
277278
get inputBoxTextModel(): ITextModel { return this._inputBoxTextModel; }
278279

279280
private readonly _contextValue = observableValue<string | undefined>(this, undefined);
@@ -309,6 +310,7 @@ class MainThreadSCMProvider implements ISCMProvider {
309310
private readonly _providerId: string,
310311
private readonly _label: string,
311312
private readonly _rootUri: URI | undefined,
313+
private readonly _iconPath: URI | { light: URI; dark: URI } | ThemeIcon | undefined,
312314
private readonly _inputBoxTextModel: ITextModel,
313315
private readonly _quickDiffService: IQuickDiffService,
314316
private readonly _uriIdentService: IUriIdentityService,
@@ -571,11 +573,11 @@ export class MainThreadSCM implements MainThreadSCMShape {
571573
this._disposables.dispose();
572574
}
573575

574-
async $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): Promise<void> {
576+
async $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined, inputBoxDocumentUri: UriComponents): Promise<void> {
575577
this._repositoryBarriers.set(handle, new Barrier());
576578

577579
const inputBoxTextModelRef = await this.textModelService.createModelReference(URI.revive(inputBoxDocumentUri));
578-
const provider = new MainThreadSCMProvider(this._proxy, handle, parentHandle, id, label, rootUri ? URI.revive(rootUri) : undefined, inputBoxTextModelRef.object.textEditorModel, this.quickDiffService, this._uriIdentService, this.workspaceContextService);
580+
const provider = new MainThreadSCMProvider(this._proxy, handle, parentHandle, id, label, rootUri ? URI.revive(rootUri) : undefined, getIconFromIconDto(iconPath), inputBoxTextModelRef.object.textEditorModel, this.quickDiffService, this._uriIdentService, this.workspaceContextService);
579581
const repository = this.scmService.registerSCMProvider(provider);
580582
this._repositories.set(handle, repository);
581583

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,11 +1273,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
12731273

12741274
return extHostSCM.getLastInputBox(extension)!; // Strict null override - Deprecated api
12751275
},
1276-
createSourceControl(id: string, label: string, rootUri?: vscode.Uri, parent?: vscode.SourceControl): vscode.SourceControl {
1277-
if (parent) {
1276+
createSourceControl(id: string, label: string, rootUri?: vscode.Uri, iconPath?: vscode.IconPath, parent?: vscode.SourceControl): vscode.SourceControl {
1277+
if (iconPath || parent) {
12781278
checkProposedApiEnabled(extension, 'scmProviderOptions');
12791279
}
1280-
return extHostSCM.createSourceControl(extension, id, label, rootUri, parent);
1280+
return extHostSCM.createSourceControl(extension, id, label, rootUri, iconPath, parent);
12811281
}
12821282
};
12831283

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1650,7 +1650,7 @@ export interface SCMHistoryItemChangeDto {
16501650
}
16511651

16521652
export interface MainThreadSCMShape extends IDisposable {
1653-
$registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): Promise<void>;
1653+
$registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined, inputBoxDocumentUri: UriComponents): Promise<void>;
16541654
$updateSourceControl(handle: number, features: SCMProviderFeatures): Promise<void>;
16551655
$unregisterSourceControl(handle: number): Promise<void>;
16561656

src/vs/workbench/api/common/extHostSCM.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,7 @@ class ExtHostSourceControl implements vscode.SourceControl {
788788
private _id: string,
789789
private _label: string,
790790
private _rootUri?: vscode.Uri,
791+
_iconPath?: vscode.IconPath,
791792
_parent?: ExtHostSourceControl
792793
) {
793794
this.#proxy = proxy;
@@ -799,7 +800,7 @@ class ExtHostSourceControl implements vscode.SourceControl {
799800
});
800801

801802
this._inputBox = new ExtHostSCMInputBox(_extension, _extHostDocuments, this.#proxy, this.handle, inputBoxDocumentUri);
802-
this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, inputBoxDocumentUri);
803+
this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, getHistoryItemIconDto(_iconPath), inputBoxDocumentUri);
803804

804805
this.onDidDisposeParent = _parent ? _parent.onDidDispose : Event.None;
805806
}
@@ -954,7 +955,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
954955
});
955956
}
956957

957-
createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl {
958+
createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, iconPath: vscode.IconPath | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl {
958959
this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri);
959960

960961
type TEvent = { extensionId: string };
@@ -968,7 +969,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
968969
});
969970

970971
const parentSourceControl = parent ? Iterable.find(this._sourceControls.values(), s => s === parent) : undefined;
971-
const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, parentSourceControl);
972+
const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, iconPath, parentSourceControl);
972973
this._sourceControls.set(sourceControl.handle, sourceControl);
973974

974975
const sourceControls = this._sourceControlsByExtension.get(extension.identifier) || [];

src/vs/workbench/contrib/scm/browser/media/scm.css

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,22 @@
4545
display: none;
4646
}
4747

48-
.scm-view .scm-provider > .label {
48+
.scm-view .scm-provider > .monaco-icon-label {
4949
flex: 1;
50-
overflow: hidden;
51-
text-overflow: ellipsis;
52-
min-width: 50px;
5350
}
5451

55-
.scm-view .scm-provider > .label > .name {
52+
.scm-view .scm-provider .monaco-highlighted-label {
53+
display: flex;
54+
align-items: center;
5655
font-weight: bold;
5756
}
5857

59-
.scm-view .scm-provider > .label > .description {
60-
opacity: 0.7;
61-
margin-left: 0.5em;
62-
font-size: 0.9em;
58+
.scm-view .scm-provider .monaco-highlighted-label .codicon {
59+
font-size: 14px;
60+
}
61+
62+
.scm-view .scm-provider .monaco-highlighted-label .codicon.codicon-archive {
63+
padding-top: 1px;
6364
}
6465

6566
.scm-view .scm-provider > .actions {
@@ -493,7 +494,7 @@
493494

494495
/* Repositories */
495496

496-
.scm-repositories-view .scm-provider > .label > .name {
497+
.scm-view.scm-repositories-view .monaco-highlighted-label {
497498
font-weight: normal;
498499
}
499500

src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actio
2424
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
2525
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
2626
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
27-
import { IManagedHover } from '../../../../base/browser/ui/hover/hover.js';
28-
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
29-
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
27+
import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js';
28+
import { ThemeIcon } from '../../../../base/common/themables.js';
3029

3130
export class RepositoryActionRunner extends ActionRunner {
3231
constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) {
@@ -46,10 +45,7 @@ export class RepositoryActionRunner extends ActionRunner {
4645
}
4746

4847
interface RepositoryTemplate {
49-
readonly label: HTMLElement;
50-
readonly labelCustomHover: IManagedHover;
51-
readonly name: HTMLElement;
52-
readonly description: HTMLElement;
48+
readonly label: IconLabel;
5349
readonly countContainer: HTMLElement;
5450
readonly count: CountBadge;
5551
readonly toolBar: WorkbenchToolBar;
@@ -68,7 +64,6 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer<ISCMReposit
6864
@ICommandService private commandService: ICommandService,
6965
@IContextKeyService private contextKeyService: IContextKeyService,
7066
@IContextMenuService private contextMenuService: IContextMenuService,
71-
@IHoverService private hoverService: IHoverService,
7267
@IKeybindingService private keybindingService: IKeybindingService,
7368
@IMenuService private menuService: IMenuService,
7469
@ISCMViewService private scmViewService: ISCMViewService,
@@ -82,31 +77,34 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer<ISCMReposit
8277
}
8378

8479
const provider = append(container, $('.scm-provider'));
85-
const label = append(provider, $('.label'));
86-
const labelCustomHover = this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), label, '', {});
87-
const name = append(label, $('span.name'));
88-
const description = append(label, $('span.description'));
80+
const label = new IconLabel(provider, { supportIcons: true });
81+
8982
const actions = append(provider, $('.actions'));
9083
const toolBar = new WorkbenchToolBar(actions, { actionViewItemProvider: this.actionViewItemProvider, resetMenu: this.toolbarMenuId }, this.menuService, this.contextKeyService, this.contextMenuService, this.keybindingService, this.commandService, this.telemetryService);
9184
const countContainer = append(provider, $('.count'));
9285
const count = new CountBadge(countContainer, {}, defaultCountBadgeStyles);
9386
const visibilityDisposable = toolBar.onDidChangeDropdownVisibility(e => provider.classList.toggle('active', e));
9487

95-
const templateDisposable = combinedDisposable(labelCustomHover, visibilityDisposable, toolBar);
88+
const templateDisposable = combinedDisposable(label, visibilityDisposable, toolBar);
9689

97-
return { label, labelCustomHover, name, description, countContainer, count, toolBar, elementDisposables: new DisposableStore(), templateDisposable };
90+
return { label, countContainer, count, toolBar, elementDisposables: new DisposableStore(), templateDisposable };
9891
}
9992

10093
renderElement(arg: ISCMRepository | ITreeNode<ISCMRepository, FuzzyScore>, index: number, templateData: RepositoryTemplate): void {
10194
const repository = isSCMRepository(arg) ? arg : arg.element;
10295

103-
templateData.name.textContent = repository.provider.name;
96+
const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath)
97+
? repository.provider.iconPath.id
98+
: undefined;
99+
100+
const label = icon
101+
? `$(${icon}) ${repository.provider.name}`
102+
: repository.provider.name;
103+
104104
if (repository.provider.rootUri) {
105-
templateData.labelCustomHover.update(`${repository.provider.label}: ${repository.provider.rootUri.fsPath}`);
106-
templateData.description.textContent = repository.provider.label;
105+
templateData.label.setLabel(label, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` });
107106
} else {
108-
templateData.labelCustomHover.update(repository.provider.label);
109-
templateData.description.textContent = '';
107+
templateData.label.setLabel(label, undefined, { title: repository.provider.label });
110108
}
111109

112110
let statusPrimaryActions: IAction[] = [];

src/vs/workbench/contrib/scm/common/scm.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export interface ISCMProvider extends IDisposable {
8080
readonly onDidChangeResources: Event<void>;
8181

8282
readonly rootUri?: URI;
83+
readonly iconPath?: URI | { light: URI; dark: URI } | ThemeIcon;
8384
readonly inputBoxTextModel: ITextModel;
8485
readonly contextValue: IObservable<string | undefined>;
8586
readonly count: IObservable<number | undefined>;

src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ declare module 'vscode' {
3434
}
3535

3636
export namespace scm {
37-
export function createSourceControl(id: string, label: string, rootUri?: Uri, parent?: SourceControl): SourceControl;
37+
export function createSourceControl(id: string, label: string, rootUri?: Uri, iconPath?: IconPath, parent?: SourceControl): SourceControl;
3838
}
3939
}

0 commit comments

Comments
 (0)