Skip to content

Commit c677510

Browse files
committed
Refines commit/file stats formatting and tooltips
- Improves branch/tag tips pill style - Adds coloring to stats
1 parent 6bf66a4 commit c677510

File tree

15 files changed

+419
-145
lines changed

15 files changed

+419
-145
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,14 +1582,14 @@
15821582
},
15831583
"gitlens.views.formats.commits.tooltip": {
15841584
"type": "string",
1585-
"default": "${avatar}  __${author}__  $(history) ${ago} _(${date})_ \\\n${link}${' via 'pullRequest}${'  •  'changesDetail} ${message}${\n\n---\n\nfootnotes}\n\n${tips}",
1585+
"default": "${avatar}  __${author}__  $(history) ${ago} _(${date})_ \\\n${link}${' via 'pullRequest}${'  'changesDetail} ${message}${\n\n---\n\nfootnotes}\n\n${tips}",
15861586
"markdownDescription": "Specifies the tooltip format (in markdown) of commits in the views. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs",
15871587
"scope": "window",
15881588
"order": 32
15891589
},
15901590
"gitlens.views.formats.commits.tooltipWithStatus": {
15911591
"type": "string",
1592-
"default": "${avatar}  __${author}__  $(history) ${ago} _(${date})_ \\\n${link}${' via 'pullRequest}  •  {{slot-status}}${'  •  'changesDetail} ${message}${\n\n---\n\nfootnotes}\n\n${tips}",
1592+
"default": "${avatar}  __${author}__  $(history) ${ago} _(${date})_ \\\n${link}${' via 'pullRequest}  •  {{slot-status}} ${message}${\n\n---\n\nfootnotes}\n\n${tips}",
15931593
"markdownDescription": "Specifies the tooltip format (in markdown) of \"file\" commits in the views. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs",
15941594
"scope": "window",
15951595
"order": 32
@@ -1624,7 +1624,7 @@
16241624
},
16251625
"gitlens.views.formats.stashes.tooltip": {
16261626
"type": "string",
1627-
"default": "${link}${' on `'stashOnRef`}${'  •  'changesDetail} \\\n  $(history) ${ago} _(${date})_ ${message}${\n\n---\n\nfootnotes}",
1627+
"default": "${link}${' on `'stashOnRef`}${'  'changesDetail} \\\n  $(history) ${ago} _(${date})_ ${message}${\n\n---\n\nfootnotes}",
16281628
"markdownDescription": "Specifies the tooltip format (in markdown) of stashes in the views. See [_Commit Tokens_](https://github.com/eamodio/vscode-gitlens/wiki/Custom-Formatting#commit-tokens) in the GitLens docs",
16291629
"scope": "window",
16301630
"order": 52

src/env/node/git/localGitProvider.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ import {
146146
import {
147147
createLogParserSingle,
148148
createLogParserWithFiles,
149+
createLogParserWithFileStats,
149150
getContributorsParser,
150151
getGraphParser,
151152
getGraphStatsParser,
@@ -2350,6 +2351,30 @@ export class LocalGitProvider implements GitProvider, Disposable {
23502351
return this.git.rev_list__count(repoPath, ref);
23512352
}
23522353

2354+
@log()
2355+
async getCommitFileStats(repoPath: string, ref: string): Promise<GitFileChange[] | undefined> {
2356+
const parser = createLogParserWithFileStats<{ sha: string }>({ sha: '%H' });
2357+
2358+
const data = await this.git.log(repoPath, { ref: ref }, '--max-count=1', ...parser.arguments);
2359+
if (data == null) return undefined;
2360+
2361+
let files: GitFileChange[] | undefined;
2362+
2363+
for (const c of parser.parse(data)) {
2364+
files = c.files.map(
2365+
f =>
2366+
new GitFileChange(repoPath, f.path, f.status as GitFileStatus, f.originalPath, undefined, {
2367+
additions: f.additions,
2368+
deletions: f.deletions,
2369+
changes: 0,
2370+
}),
2371+
);
2372+
break;
2373+
}
2374+
2375+
return files;
2376+
}
2377+
23532378
@log()
23542379
async getCommitForFile(
23552380
repoPath: string | undefined,
@@ -3664,7 +3689,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
36643689
ref?: string;
36653690
since?: number | string;
36663691
stashes?: boolean | Map<string, GitStashCommit>;
3667-
status?: null | 'name-status' | 'numstat' | 'stat';
3692+
status?: boolean;
36683693
until?: number | string;
36693694
extraArgs?: string[];
36703695
},
@@ -3679,8 +3704,8 @@ export class LocalGitProvider implements GitProvider, Disposable {
36793704
`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`,
36803705
];
36813706

3682-
if (options?.status !== null) {
3683-
args.push(`--${options?.status ?? 'name-status'}`, '--full-history');
3707+
if (options?.status !== false) {
3708+
args.push('--name-status', '--full-history');
36843709
}
36853710
if (options?.all) {
36863711
args.push('--all');

src/git/formatters/commitFormatter.ts

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ export interface CommitFormatOptions extends FormatOptions {
4848
dateStyle?: DateStyle;
4949
editor?: { line: number; uri: Uri };
5050
footnotes?: Map<number, string>;
51-
getBranchAndTagTips?: (sha: string, options?: { compact?: boolean; icons?: boolean }) => string | undefined;
51+
getBranchAndTagTips?: (
52+
sha: string,
53+
options?: { compact?: boolean; icons?: boolean; pills?: boolean | { cssClass: string } },
54+
) => string | undefined;
5255
htmlFormat?: {
5356
classes?: {
5457
author?: string;
@@ -358,24 +361,45 @@ export class CommitFormatter extends Formatter<GitCommit, CommitFormatOptions> {
358361
}
359362

360363
get changes(): string {
361-
return this._padOrTruncate(
362-
isCommit(this._item) ? this._item.formatStats() : '',
363-
this._options.tokenOptions.changes,
364+
if (!isCommit(this._item) || this._item.stats == null) {
365+
return this._padOrTruncate('', this._options.tokenOptions.changes);
366+
}
367+
368+
const stats = this._item.formatStats(
369+
'stats',
370+
this._options.outputFormat !== 'plaintext' ? { color: true } : undefined,
364371
);
372+
return this._padOrTruncate(stats, this._options.tokenOptions.changes);
365373
}
366374

367375
get changesDetail(): string {
368-
return this._padOrTruncate(
369-
isCommit(this._item) ? this._item.formatStats({ expand: true, separator: ', ' }) : '',
370-
this._options.tokenOptions.changesDetail,
376+
if (!isCommit(this._item) || this._item.stats == null) {
377+
return this._padOrTruncate('', this._options.tokenOptions.changesDetail);
378+
}
379+
380+
let stats = this._item.formatStats(
381+
'stats',
382+
this._options.outputFormat !== 'plaintext' ? { color: true } : undefined,
371383
);
384+
const statsExpanded = this._item.formatStats('expanded', {
385+
addParenthesesToFileStats: true,
386+
color: this._options.outputFormat !== 'plaintext',
387+
separator: ', ',
388+
});
389+
if (statsExpanded) {
390+
stats += ` ${statsExpanded}`;
391+
}
392+
393+
return this._padOrTruncate(stats, this._options.tokenOptions.changesDetail);
372394
}
373395

374396
get changesShort(): string {
375-
return this._padOrTruncate(
376-
isCommit(this._item) ? this._item.formatStats({ compact: true, separator: '' }) : '',
377-
this._options.tokenOptions.changesShort,
378-
);
397+
if (!isCommit(this._item) || this._item.stats == null) {
398+
return this._padOrTruncate('', this._options.tokenOptions.changesShort);
399+
}
400+
401+
const stats = this._item.formatStats('short', { separator: '' });
402+
return this._padOrTruncate(stats, this._options.tokenOptions.changesShort);
379403
}
380404

381405
get commands(): string {
@@ -835,22 +859,17 @@ export class CommitFormatter extends Formatter<GitCommit, CommitFormatOptions> {
835859
}
836860

837861
get tips(): string {
838-
let branchAndTagTips = this._options.getBranchAndTagTips?.(this._item.sha, {
862+
const branchAndTagTips = this._options.getBranchAndTagTips?.(this._item.sha, {
839863
icons: this._options.outputFormat === 'markdown',
864+
pills:
865+
this._options.outputFormat === 'markdown'
866+
? true
867+
: this._options.outputFormat === 'html'
868+
? this._options.htmlFormat?.classes?.tips
869+
? { cssClass: this._options.htmlFormat.classes.tips }
870+
: true
871+
: false,
840872
});
841-
if (branchAndTagTips != null && this._options.outputFormat !== 'plaintext') {
842-
const tips = branchAndTagTips.split(', ');
843-
branchAndTagTips = tips
844-
.map(
845-
t =>
846-
/*html*/ `<span style="color:#ffffff;background-color:#1d76db;"${
847-
this._options.htmlFormat?.classes?.tips
848-
? ` class="${this._options.htmlFormat.classes.tips}"`
849-
: ''
850-
}>&nbsp;&nbsp;${t}&nbsp;&nbsp;</span>`,
851-
)
852-
.join(GlyphChars.Space.repeat(3));
853-
}
854873
return this._padOrTruncate(branchAndTagTips ?? '', this._options.tokenOptions.tips);
855874
}
856875

src/git/formatters/statusFormatter.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,34 @@ export class StatusFileFormatter extends Formatter<GitFile, StatusFormatOptions>
9393
}
9494

9595
get changes(): string {
96+
if (!isGitFileChange(this._item)) {
97+
return this._padOrTruncate('', this._options.tokenOptions.changes);
98+
}
99+
96100
return this._padOrTruncate(
97-
isGitFileChange(this._item) ? this._item.formatStats() : '',
101+
this._item.formatStats('stats', this._options.outputFormat !== 'plaintext' ? { color: true } : undefined),
98102
this._options.tokenOptions.changes,
99103
);
100104
}
101105

102106
get changesDetail(): string {
107+
if (!isGitFileChange(this._item)) {
108+
return this._padOrTruncate('', this._options.tokenOptions.changes);
109+
}
110+
103111
return this._padOrTruncate(
104-
isGitFileChange(this._item) ? this._item.formatStats({ expand: true, separator: ', ' }) : '',
112+
this._item.formatStats('expanded', { color: this._options.outputFormat !== 'plaintext', separator: ', ' }),
105113
this._options.tokenOptions.changesDetail,
106114
);
107115
}
108116

109117
get changesShort(): string {
118+
if (!isGitFileChange(this._item)) {
119+
return this._padOrTruncate('', this._options.tokenOptions.changes);
120+
}
121+
110122
return this._padOrTruncate(
111-
isGitFileChange(this._item) ? this._item.formatStats({ compact: true, separator: '' }) : '',
123+
this._item.formatStats('short', { separator: '' }),
112124
this._options.tokenOptions.changesShort,
113125
);
114126
}

src/git/gitProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { BranchSortOptions, GitBranch } from './models/branch';
1010
import type { GitCommit } from './models/commit';
1111
import type { GitContributor, GitContributorStats } from './models/contributor';
1212
import type { GitDiff, GitDiffFile, GitDiffFiles, GitDiffFilter, GitDiffLine, GitDiffShortStat } from './models/diff';
13-
import type { GitFile } from './models/file';
13+
import type { GitFile, GitFileChange } from './models/file';
1414
import type { GitGraph } from './models/graph';
1515
import type { GitLog } from './models/log';
1616
import type { GitMergeStatus } from './models/merge';
@@ -197,6 +197,7 @@ export interface GitProviderRepository {
197197
| { commitDate?: Date; mode?: 'contains' | 'pointsAt'; remotes?: boolean },
198198
): Promise<string[]>;
199199
getCommitCount(repoPath: string, ref: string): Promise<number | undefined>;
200+
getCommitFileStats?(repoPath: string | Uri, ref: string): Promise<GitFileChange[] | undefined>;
200201
getCommitForFile(
201202
repoPath: string,
202203
uri: Uri,

src/git/gitProviderService.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import { deletedOrMissing, uncommitted, uncommittedStaged } from './models/const
6767
import type { GitContributor, GitContributorStats } from './models/contributor';
6868
import { calculateDistribution } from './models/contributor';
6969
import type { GitDiff, GitDiffFile, GitDiffFiles, GitDiffFilter, GitDiffLine, GitDiffShortStat } from './models/diff';
70-
import type { GitFile } from './models/file';
70+
import type { GitFile, GitFileChange } from './models/file';
7171
import type { GitGraph } from './models/graph';
7272
import type { GitLog } from './models/log';
7373
import type { GitMergeStatus } from './models/merge';
@@ -1720,10 +1720,20 @@ export class GitProviderService implements Disposable {
17201720
repoPath: string | Uri | undefined,
17211721
suppressName?: string,
17221722
): Promise<
1723-
(sha: string, options?: { compact?: boolean | undefined; icons?: boolean | undefined }) => string | undefined
1723+
(
1724+
sha: string,
1725+
options?: { compact?: boolean; icons?: boolean; pills?: boolean | { cssClass: string } },
1726+
) => string | undefined
17241727
> {
17251728
if (repoPath == null) return () => undefined;
17261729

1730+
type Tip = {
1731+
name: string;
1732+
icon: string;
1733+
compactName: string | undefined;
1734+
type: 'branch' | 'tag';
1735+
};
1736+
17271737
const [branchesResult, tagsResult, remotesResult] = await Promise.allSettled([
17281738
this.getBranches(repoPath),
17291739
this.getTags(repoPath),
@@ -1744,7 +1754,7 @@ export class GitProviderService implements Disposable {
17441754
const remote = remotes.find(r => r.name === bt.getRemoteName());
17451755
icon = `$(${getRemoteThemeIconString(remote)}) `;
17461756
} else {
1747-
icon = '$(git-branch) ';
1757+
icon = bt.current ? '$(target) ' : '$(git-branch) ';
17481758
}
17491759
} else {
17501760
icon = '$(tag) ';
@@ -1758,11 +1768,14 @@ export class GitProviderService implements Disposable {
17581768
? bt.getRemoteName()
17591769
: undefined,
17601770
type: bt.refType,
1761-
};
1771+
} satisfies Tip;
17621772
},
17631773
);
17641774

1765-
return (sha: string, options?: { compact?: boolean; icons?: boolean }): string | undefined => {
1775+
return (
1776+
sha: string,
1777+
options?: { compact?: boolean; icons?: boolean; pills?: boolean | { cssClass: string } },
1778+
): string | undefined => {
17661779
const branchesAndTags = branchesAndTagsBySha.get(sha);
17671780
if (!branchesAndTags?.length) return undefined;
17681781

@@ -1771,16 +1784,32 @@ export class GitProviderService implements Disposable {
17711784
? branchesAndTags.filter(bt => bt.name !== suppressName)
17721785
: branchesAndTags;
17731786

1774-
if (!options?.compact) {
1775-
return tips.map(bt => `${options?.icons ? bt.icon : ''}${bt.name}`).join(', ');
1787+
function getIconAndLabel(tip: Tip) {
1788+
const label = (options?.compact ? tip.compactName : undefined) ?? tip.name;
1789+
return `${options?.icons ? `${tip.icon}${options?.pills ? '&nbsp;' : ' '}` : ''}${label}`;
17761790
}
17771791

1778-
if (tips.length > 1) {
1792+
let results;
1793+
if (options?.compact) {
1794+
if (!tips.length) return undefined;
1795+
17791796
const [bt] = tips;
1780-
return `${options?.icons ? bt.icon : ''}${bt.compactName ?? bt.name}, ${GlyphChars.Ellipsis}`;
1797+
results = [`${getIconAndLabel(bt)}${tips.length > 1 ? `, ${GlyphChars.Ellipsis}` : ''}`];
1798+
} else {
1799+
results = tips.map(getIconAndLabel);
17811800
}
17821801

1783-
return tips.map(bt => `${options?.icons ? bt.icon : ''}${bt.compactName ?? bt.name}`).join(', ');
1802+
if (options?.pills) {
1803+
return results
1804+
.map(
1805+
t =>
1806+
/*html*/ `<span style="color:#ffffff;background-color:#1d76db;border-radius:3px;"${
1807+
typeof options.pills === 'object' ? ` class="${options.pills.cssClass}"` : ''
1808+
}>&nbsp;${t}&nbsp;&nbsp;</span>`,
1809+
)
1810+
.join('&nbsp;&nbsp;');
1811+
}
1812+
return results.join(', ');
17841813
};
17851814
}
17861815

@@ -1834,6 +1863,12 @@ export class GitProviderService implements Disposable {
18341863
return provider.getCommitCount(path, ref);
18351864
}
18361865

1866+
@log()
1867+
async getCommitFileStats(repoPath: string | Uri, ref: string): Promise<GitFileChange[] | undefined> {
1868+
const { provider, path } = this.getProvider(repoPath);
1869+
return provider.getCommitFileStats?.(path, ref);
1870+
}
1871+
18371872
@log()
18381873
async getCommitForFile(
18391874
repoPath: string | Uri | undefined,

0 commit comments

Comments
 (0)