diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index fc985ae7d06..ff68d91cb36 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -7,7 +7,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import * as fs from 'fs'; import * as path from 'path'; import picomatch from 'picomatch'; -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'; +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'; import { ActionButton } from './actionButton'; import { ApiRepository } from './api/api1'; 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 { : undefined; const parent = this.repositoryResolver.getRepository(parentRoot)?.sourceControl; + // Icon + const icon = repository.kind === 'submodule' + ? new ThemeIcon('archive') + : repository.kind === 'worktree' + ? new ThemeIcon('list-tree') + : new ThemeIcon('repo'); + const root = Uri.file(repository.root); - this._sourceControl = scm.createSourceControl('git', 'Git', root, parent); + this._sourceControl = scm.createSourceControl('git', 'Git', root, icon, parent); this._sourceControl.contextValue = repository.kind; this._sourceControl.quickDiffProvider = this; diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 03deb6b564c..68b32c43584 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -274,6 +274,7 @@ class MainThreadSCMProvider implements ISCMProvider { get handle(): number { return this._handle; } get label(): string { return this._label; } get rootUri(): URI | undefined { return this._rootUri; } + get iconPath(): URI | { light: URI; dark: URI } | ThemeIcon | undefined { return this._iconPath; } get inputBoxTextModel(): ITextModel { return this._inputBoxTextModel; } private readonly _contextValue = observableValue(this, undefined); @@ -309,6 +310,7 @@ class MainThreadSCMProvider implements ISCMProvider { private readonly _providerId: string, private readonly _label: string, private readonly _rootUri: URI | undefined, + private readonly _iconPath: URI | { light: URI; dark: URI } | ThemeIcon | undefined, private readonly _inputBoxTextModel: ITextModel, private readonly _quickDiffService: IQuickDiffService, private readonly _uriIdentService: IUriIdentityService, @@ -571,11 +573,11 @@ export class MainThreadSCM implements MainThreadSCMShape { this._disposables.dispose(); } - async $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): Promise { + 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 { this._repositoryBarriers.set(handle, new Barrier()); const inputBoxTextModelRef = await this.textModelService.createModelReference(URI.revive(inputBoxDocumentUri)); - const provider = new MainThreadSCMProvider(this._proxy, handle, parentHandle, id, label, rootUri ? URI.revive(rootUri) : undefined, inputBoxTextModelRef.object.textEditorModel, this.quickDiffService, this._uriIdentService, this.workspaceContextService); + 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); const repository = this.scmService.registerSCMProvider(provider); this._repositories.set(handle, repository); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 12f65be5daa..7d3fdc548ef 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1273,11 +1273,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostSCM.getLastInputBox(extension)!; // Strict null override - Deprecated api }, - createSourceControl(id: string, label: string, rootUri?: vscode.Uri, parent?: vscode.SourceControl): vscode.SourceControl { - if (parent) { + createSourceControl(id: string, label: string, rootUri?: vscode.Uri, iconPath?: vscode.IconPath, parent?: vscode.SourceControl): vscode.SourceControl { + if (iconPath || parent) { checkProposedApiEnabled(extension, 'scmProviderOptions'); } - return extHostSCM.createSourceControl(extension, id, label, rootUri, parent); + return extHostSCM.createSourceControl(extension, id, label, rootUri, iconPath, parent); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c124276909c..06da782e1aa 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1650,7 +1650,7 @@ export interface SCMHistoryItemChangeDto { } export interface MainThreadSCMShape extends IDisposable { - $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, inputBoxDocumentUri: UriComponents): Promise; + $registerSourceControl(handle: number, parentHandle: number | undefined, id: string, label: string, rootUri: UriComponents | undefined, iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined, inputBoxDocumentUri: UriComponents): Promise; $updateSourceControl(handle: number, features: SCMProviderFeatures): Promise; $unregisterSourceControl(handle: number): Promise; diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 4f72b7975d9..3a32a9c635b 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -788,6 +788,7 @@ class ExtHostSourceControl implements vscode.SourceControl { private _id: string, private _label: string, private _rootUri?: vscode.Uri, + _iconPath?: vscode.IconPath, _parent?: ExtHostSourceControl ) { this.#proxy = proxy; @@ -799,7 +800,7 @@ class ExtHostSourceControl implements vscode.SourceControl { }); this._inputBox = new ExtHostSCMInputBox(_extension, _extHostDocuments, this.#proxy, this.handle, inputBoxDocumentUri); - this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, inputBoxDocumentUri); + this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, getHistoryItemIconDto(_iconPath), inputBoxDocumentUri); this.onDidDisposeParent = _parent ? _parent.onDidDispose : Event.None; } @@ -954,7 +955,7 @@ export class ExtHostSCM implements ExtHostSCMShape { }); } - createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl { + createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, iconPath: vscode.IconPath | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl { this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri); type TEvent = { extensionId: string }; @@ -968,7 +969,7 @@ export class ExtHostSCM implements ExtHostSCMShape { }); const parentSourceControl = parent ? Iterable.find(this._sourceControls.values(), s => s === parent) : undefined; - const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, parentSourceControl); + const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, iconPath, parentSourceControl); this._sourceControls.set(sourceControl.handle, sourceControl); const sourceControls = this._sourceControlsByExtension.get(extension.identifier) || []; diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 97133d6a4b4..d670123008b 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -45,21 +45,22 @@ display: none; } -.scm-view .scm-provider > .label { +.scm-view .scm-provider > .monaco-icon-label { flex: 1; - overflow: hidden; - text-overflow: ellipsis; - min-width: 50px; } -.scm-view .scm-provider > .label > .name { +.scm-view .scm-provider .monaco-highlighted-label { + display: flex; + align-items: center; font-weight: bold; } -.scm-view .scm-provider > .label > .description { - opacity: 0.7; - margin-left: 0.5em; - font-size: 0.9em; +.scm-view .scm-provider .monaco-highlighted-label .codicon { + font-size: 14px; +} + +.scm-view .scm-provider .monaco-highlighted-label .codicon.codicon-archive { + padding-top: 1px; } .scm-view .scm-provider > .actions { @@ -493,7 +494,7 @@ /* Repositories */ -.scm-repositories-view .scm-provider > .label > .name { +.scm-view.scm-repositories-view .monaco-highlighted-label { font-weight: normal; } diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 3719c27eb6d..06afb199824 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -24,9 +24,8 @@ import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actio import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { IManagedHover } from '../../../../base/browser/ui/hover/hover.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; -import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; +import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; export class RepositoryActionRunner extends ActionRunner { constructor(private readonly getSelectedRepositories: () => ISCMRepository[]) { @@ -46,10 +45,7 @@ export class RepositoryActionRunner extends ActionRunner { } interface RepositoryTemplate { - readonly label: HTMLElement; - readonly labelCustomHover: IManagedHover; - readonly name: HTMLElement; - readonly description: HTMLElement; + readonly label: IconLabel; readonly countContainer: HTMLElement; readonly count: CountBadge; readonly toolBar: WorkbenchToolBar; @@ -68,7 +64,6 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer provider.classList.toggle('active', e)); - const templateDisposable = combinedDisposable(labelCustomHover, visibilityDisposable, toolBar); + const templateDisposable = combinedDisposable(label, visibilityDisposable, toolBar); - return { label, labelCustomHover, name, description, countContainer, count, toolBar, elementDisposables: new DisposableStore(), templateDisposable }; + return { label, countContainer, count, toolBar, elementDisposables: new DisposableStore(), templateDisposable }; } renderElement(arg: ISCMRepository | ITreeNode, index: number, templateData: RepositoryTemplate): void { const repository = isSCMRepository(arg) ? arg : arg.element; - templateData.name.textContent = repository.provider.name; + const icon = ThemeIcon.isThemeIcon(repository.provider.iconPath) + ? repository.provider.iconPath.id + : undefined; + + const label = icon + ? `$(${icon}) ${repository.provider.name}` + : repository.provider.name; + if (repository.provider.rootUri) { - templateData.labelCustomHover.update(`${repository.provider.label}: ${repository.provider.rootUri.fsPath}`); - templateData.description.textContent = repository.provider.label; + templateData.label.setLabel(label, repository.provider.label, { title: `${repository.provider.label}: ${repository.provider.rootUri.fsPath}` }); } else { - templateData.labelCustomHover.update(repository.provider.label); - templateData.description.textContent = ''; + templateData.label.setLabel(label, undefined, { title: repository.provider.label }); } let statusPrimaryActions: IAction[] = []; diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index f51279a6030..b30dbc610ea 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -80,6 +80,7 @@ export interface ISCMProvider extends IDisposable { readonly onDidChangeResources: Event; readonly rootUri?: URI; + readonly iconPath?: URI | { light: URI; dark: URI } | ThemeIcon; readonly inputBoxTextModel: ITextModel; readonly contextValue: IObservable; readonly count: IObservable; diff --git a/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts b/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts index b14c5ce1419..b7869f61da8 100644 --- a/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts +++ b/src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts @@ -34,6 +34,6 @@ declare module 'vscode' { } export namespace scm { - export function createSourceControl(id: string, label: string, rootUri?: Uri, parent?: SourceControl): SourceControl; + export function createSourceControl(id: string, label: string, rootUri?: Uri, iconPath?: IconPath, parent?: SourceControl): SourceControl; } }