From 1027a1aad084be1ef10fe49b99d7150e2a07f2aa Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 3 Dec 2025 07:09:25 +0000 Subject: [PATCH] SCM - consistently render date on the right in the repositories view (#280870) SCM - scaffold artifact timestamp --- extensions/git/src/artifactProvider.ts | 16 ++--- .../workbench/api/common/extHost.protocol.ts | 1 + .../contrib/scm/browser/media/scm.css | 27 ++++++++ .../scm/browser/scmRepositoriesViewPane.ts | 66 +++++++++++++++---- .../workbench/contrib/scm/common/artifact.ts | 2 + .../vscode.proposed.scmArtifactProvider.d.ts | 1 + 6 files changed, 92 insertions(+), 21 deletions(-) diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts index f6e4c255918..9defe57d9bc 100644 --- a/extensions/git/src/artifactProvider.ts +++ b/extensions/git/src/artifactProvider.ts @@ -4,16 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable } from 'vscode'; -import { dispose, filterEvent, fromNow, getStashDescription, IDisposable } from './util'; +import { dispose, filterEvent, IDisposable } from './util'; import { Repository } from './repository'; import { Ref, RefType } from './api/git'; import { OperationKind } from './operation'; function getArtifactDescription(ref: Ref, shortCommitLength: number): string { const segments: string[] = []; - if (ref.commitDetails?.commitDate) { - segments.push(fromNow(ref.commitDetails.commitDate)); - } if (ref.commit) { segments.push(ref.commit.substring(0, shortCommitLength)); } @@ -130,7 +127,8 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp description: getArtifactDescription(r, shortCommitLength), icon: this.repository.HEAD?.type === RefType.Head && r.name === this.repository.HEAD?.name ? new ThemeIcon('target') - : new ThemeIcon('git-branch') + : new ThemeIcon('git-branch'), + timestamp: r.commitDetails?.commitDate?.getTime() })); } else if (group === 'tags') { const refs = await this.repository @@ -142,7 +140,8 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp description: getArtifactDescription(r, shortCommitLength), icon: this.repository.HEAD?.type === RefType.Tag && r.name === this.repository.HEAD?.name ? new ThemeIcon('target') - : new ThemeIcon('tag') + : new ThemeIcon('tag'), + timestamp: r.commitDetails?.commitDate?.getTime() })); } else if (group === 'stashes') { const stashes = await this.repository.getStashes(); @@ -150,8 +149,9 @@ export class GitArtifactProvider implements SourceControlArtifactProvider, IDisp return stashes.map(s => ({ id: `stash@{${s.index}}`, name: s.description, - description: getStashDescription(s), - icon: new ThemeIcon('git-stash') + description: s.branchName, + icon: new ThemeIcon('git-stash'), + timestamp: s.commitDate?.getTime() })); } } catch (err) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c34c4465f71..6121b90c36f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1765,6 +1765,7 @@ export interface SCMArtifactDto { readonly name: string; readonly description?: string; readonly icon?: UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon; + readonly timestamp?: number; } export interface MainThreadSCMShape extends IDisposable { diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 7008508d1e2..fc5382a2164 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -585,6 +585,33 @@ white-space: nowrap; } +.scm-repositories-view .scm-artifact .timestamp-container { + flex-shrink: 0; + margin-left: 2px; + margin-right: 4px; + opacity: 0.5; +} + +.scm-repositories-view .scm-artifact .timestamp-container.duplicate { + height: 22px; + min-width: 6px; + border-left: 1px solid currentColor; + opacity: 0.25; + + .timestamp { + display: none; + } +} + +.scm-repositories-view .monaco-list .monaco-list-row:hover .scm-artifact .timestamp-container.duplicate { + border-left: 0; + opacity: 0.5; + + .timestamp { + display: block; + } +} + /* History item hover */ .monaco-hover.history-item-hover .history-item-hover-container > .rendered-markdown:first-child > p { diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index 754308fa34e..36fd4f0247f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -46,6 +46,7 @@ import { IAsyncDataTreeViewState, ITreeCompressionDelegate } from '../../../../b import { Codicon } from '../../../../base/common/codicons.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { fromNow } from '../../../../base/common/date.js'; type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement | IResourceNode; @@ -136,6 +137,8 @@ class ArtifactGroupRenderer implements ICompressibleTreeRenderer, FuzzyScore>, index: number, templateData: ArtifactTemplate): void { @@ -186,10 +192,18 @@ class ArtifactRenderer implements ICompressibleTreeRenderer(inputOrElement); - for (const artifact of artifacts) { - artifactsTree.add(URI.from({ - scheme: 'scm-artifact', path: artifact.name - }), { + for (let index = 0; index < artifacts.length; index++) { + const artifact = artifacts[index]; + const artifactUri = URI.from({ scheme: 'scm-artifact', path: artifact.name }); + const artifactBasename = artifact.id.lastIndexOf('/') > 0 + ? artifact.id.substring(0, artifact.id.lastIndexOf('/')) + : artifact.id; + + const prevArtifact = index > 0 ? artifacts[index - 1] : undefined; + const prevArtifactBasename = prevArtifact && prevArtifact.id.lastIndexOf('/') > 0 + ? prevArtifact.id.substring(0, prevArtifact.id.lastIndexOf('/')) + : prevArtifact?.id; + + const hideTimestamp = index > 0 && + artifact.timestamp !== undefined && + prevArtifact?.timestamp !== undefined && + artifactBasename === prevArtifactBasename && + fromNow(prevArtifact.timestamp) === fromNow(artifact.timestamp); + + artifactsTree.add(artifactUri, { repository, group: inputOrElement.artifactGroup, artifact, + hideTimestamp, type: 'artifact' }); } @@ -315,14 +353,16 @@ class RepositoryTreeDataSource extends Disposable implements IAsyncDataSource ( - { - repository, - group: inputOrElement.artifactGroup, - artifact, - type: 'artifact' - } satisfies SCMArtifactTreeElement - )); + return artifacts.map((artifact, index, artifacts) => ({ + repository, + group: inputOrElement.artifactGroup, + artifact, + hideTimestamp: index > 0 && + artifact.timestamp !== undefined && + artifacts[index - 1].timestamp !== undefined && + fromNow(artifacts[index - 1].timestamp!) === fromNow(artifact.timestamp), + type: 'artifact' + } satisfies SCMArtifactTreeElement)); } else if (isSCMArtifactNode(inputOrElement)) { return Iterable.map(inputOrElement.children, node => node.element && node.childrenCount === 0 ? node.element : node); diff --git a/src/vs/workbench/contrib/scm/common/artifact.ts b/src/vs/workbench/contrib/scm/common/artifact.ts index 6cee8d6b19e..03abea9c63a 100644 --- a/src/vs/workbench/contrib/scm/common/artifact.ts +++ b/src/vs/workbench/contrib/scm/common/artifact.ts @@ -26,6 +26,7 @@ export interface ISCMArtifact { readonly name: string; readonly description?: string; readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; + readonly timestamp?: number; } export interface SCMArtifactGroupTreeElement { @@ -38,5 +39,6 @@ export interface SCMArtifactTreeElement { readonly repository: ISCMRepository; readonly group: ISCMArtifactGroup; readonly artifact: ISCMArtifact; + readonly hideTimestamp: boolean; readonly type: 'artifact'; } diff --git a/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts b/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts index 6f79d3932fd..bedb671f3f8 100644 --- a/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts @@ -29,5 +29,6 @@ declare module 'vscode' { readonly name: string; readonly description?: string; readonly icon?: IconPath; + readonly timestamp?: number; } }