Skip to content

Commit 82d335b

Browse files
authored
Git - πŸ’„ consolidate git blame and timeline hover code (microsoft#269689)
* Git - πŸ’„ consolidate git blame and timeline hover code * Git - Delete code that was commented out
1 parent 3282e87 commit 82d335b

3 files changed

Lines changed: 132 additions & 138 deletions

File tree

β€Žextensions/git/src/blame.tsβ€Ž

Lines changed: 14 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation }
1515
import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider';
1616
import { AvatarQuery, AvatarQueryCommit } from './api/git';
1717
import { LRUCache } from './cache';
18-
19-
const AVATAR_SIZE = 20;
18+
import { AVATAR_SIZE, getHistoryItemHover, getHistoryItemHoverCommitHashCommands, processHistoryItemRemoteHoverCommands } from './historyProvider';
2019

2120
function lineRangesContainLine(changes: readonly TextEditorChange[], lineNumber: number): boolean {
2221
return changes.some(c => c.modified.startLineNumber <= lineNumber && lineNumber < c.modified.endLineNumberExclusive);
@@ -242,80 +241,26 @@ export class GitBlameController {
242241
this._model, repository, commitInformation?.message ?? blameInformation.subject ?? '');
243242
}
244243

245-
const markdownString = new MarkdownString();
246-
markdownString.isTrusted = true;
247-
markdownString.supportThemeIcons = true;
248-
249-
// Author, date
250244
const hash = commitInformation?.hash ?? blameInformation.hash;
251245
const authorName = commitInformation?.authorName ?? blameInformation.authorName;
252246
const authorEmail = commitInformation?.authorEmail ?? blameInformation.authorEmail;
253247
const authorDate = commitInformation?.authorDate ?? blameInformation.authorDate;
254-
const avatar = commitAvatar ? `![${authorName}](${commitAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)';
255-
256-
257-
if (authorName) {
258-
if (authorEmail) {
259-
const emailTitle = l10n.t('Email');
260-
markdownString.appendMarkdown(`${avatar} [**${authorName}**](mailto:${authorEmail} "${emailTitle} ${authorName}")`);
261-
} else {
262-
markdownString.appendMarkdown(`${avatar} **${authorName}**`);
263-
}
264-
265-
if (authorDate) {
266-
const dateString = new Date(authorDate).toLocaleString(undefined, {
267-
year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'
268-
});
269-
markdownString.appendMarkdown(`, $(history) ${fromNow(authorDate, true, true)} (${dateString})`);
270-
}
271-
272-
markdownString.appendMarkdown('\n\n');
273-
}
274-
275-
// Subject | Message
276248
const message = commitMessageWithLinks ?? commitInformation?.message ?? blameInformation.subject ?? '';
277-
markdownString.appendMarkdown(`${emojify(message.replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`);
278-
markdownString.appendMarkdown(`---\n\n`);
279-
280-
// Short stats
281-
if (commitInformation?.shortStat) {
282-
markdownString.appendMarkdown(`<span>${commitInformation.shortStat.files === 1 ?
283-
l10n.t('{0} file changed', commitInformation.shortStat.files) :
284-
l10n.t('{0} files changed', commitInformation.shortStat.files)}</span>`);
285-
286-
if (commitInformation.shortStat.insertions) {
287-
markdownString.appendMarkdown(`,&nbsp;<span style="color:var(--vscode-scmGraph-historyItemHoverAdditionsForeground);">${commitInformation.shortStat.insertions === 1 ?
288-
l10n.t('{0} insertion{1}', commitInformation.shortStat.insertions, '(+)') :
289-
l10n.t('{0} insertions{1}', commitInformation.shortStat.insertions, '(+)')}</span>`);
290-
}
291-
292-
if (commitInformation.shortStat.deletions) {
293-
markdownString.appendMarkdown(`,&nbsp;<span style="color:var(--vscode-scmGraph-historyItemHoverDeletionsForeground);">${commitInformation.shortStat.deletions === 1 ?
294-
l10n.t('{0} deletion{1}', commitInformation.shortStat.deletions, '(-)') :
295-
l10n.t('{0} deletions{1}', commitInformation.shortStat.deletions, '(-)')}</span>`);
296-
}
297-
298-
markdownString.appendMarkdown(`\n\n---\n\n`);
299-
}
300249

301250
// Commands
302-
markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, hash, documentUri]))} "${l10n.t('Open Commit')}")`);
303-
markdownString.appendMarkdown('&nbsp;');
304-
markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`);
305-
306-
// Remote hover commands
307-
if (remoteHoverCommands.length > 0) {
308-
markdownString.appendMarkdown('&nbsp;&nbsp;|&nbsp;&nbsp;');
309-
310-
const remoteCommandsMarkdown = remoteHoverCommands
311-
.map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`);
312-
markdownString.appendMarkdown(remoteCommandsMarkdown.join('&nbsp;'));
313-
}
314-
315-
markdownString.appendMarkdown('&nbsp;&nbsp;|&nbsp;&nbsp;');
316-
markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`);
317-
318-
return markdownString;
251+
const commands: Command[][] = [
252+
getHistoryItemHoverCommitHashCommands(documentUri, hash),
253+
processHistoryItemRemoteHoverCommands(remoteHoverCommands, hash)
254+
];
255+
256+
commands.push([{
257+
title: `$(gear)`,
258+
tooltip: l10n.t('Open Settings'),
259+
command: 'workbench.action.openSettings',
260+
arguments: ['git.blame']
261+
}] satisfies Command[]);
262+
263+
return getHistoryItemHover(commitAvatar, authorName, authorEmail, authorDate, message, commitInformation?.shortStat, commands);
319264
}
320265

321266
private _onDidChangeConfiguration(e?: ConfigurationChangeEvent): void {

β€Žextensions/git/src/historyProvider.tsβ€Ž

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
*--------------------------------------------------------------------------------------------*/
55

66

7-
import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent } from 'vscode';
7+
import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent, MarkdownString, Command } from 'vscode';
88
import { Repository, Resource } from './repository';
9-
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, subject, truncate } from './util';
9+
import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, fromNow, getCommitShortHash, subject, truncate } from './util';
1010
import { toMultiFileDiffEditorUris } from './uri';
1111
import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from './api/git';
1212
import { emojify, ensureEmojis } from './emoji';
13-
import { Commit } from './git';
13+
import { Commit, CommitShortStat } from './git';
1414
import { OperationKind, OperationResult } from './operation';
1515
import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider';
1616
import { throttle } from './decorators';
@@ -590,3 +590,107 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
590590
dispose(this.disposables);
591591
}
592592
}
593+
594+
export const AVATAR_SIZE = 20;
595+
596+
export function getHistoryItemHoverCommitHashCommands(documentUri: Uri, hash: string): Command[] {
597+
return [{
598+
title: `$(git-commit) ${getCommitShortHash(documentUri, hash)}`,
599+
tooltip: l10n.t('Open Commit'),
600+
command: 'git.viewCommit',
601+
arguments: [documentUri, hash, documentUri]
602+
}, {
603+
title: `$(copy)`,
604+
tooltip: l10n.t('Copy Commit Hash'),
605+
command: 'git.copyContentToClipboard',
606+
arguments: [hash]
607+
}] satisfies Command[];
608+
}
609+
610+
export function processHistoryItemRemoteHoverCommands(commands: Command[], hash: string): Command[] {
611+
return commands.map(command => ({
612+
...command,
613+
arguments: [...command.arguments ?? [], hash]
614+
} satisfies Command));
615+
}
616+
617+
export function getHistoryItemHover(authorAvatar: string | undefined, authorName: string | undefined, authorEmail: string | undefined, authorDate: Date | number | undefined, message: string, shortStats: CommitShortStat | undefined, commands: Command[][] | undefined): MarkdownString {
618+
const markdownString = new MarkdownString('', true);
619+
markdownString.isTrusted = true;
620+
621+
if (authorName) {
622+
const avatar = authorAvatar ? `![${authorName}](${authorAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)';
623+
624+
if (authorEmail) {
625+
const emailTitle = l10n.t('Email');
626+
markdownString.appendMarkdown(`${avatar} [**${authorName}**](mailto:${authorEmail} "${emailTitle} ${authorName}")`);
627+
} else {
628+
markdownString.appendMarkdown(`${avatar} **${authorName}**`);
629+
}
630+
631+
if (authorDate) {
632+
const dateString = new Date(authorDate).toLocaleString(undefined, {
633+
year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'
634+
});
635+
markdownString.appendMarkdown(`, $(history) ${fromNow(authorDate, true, true)} (${dateString})`);
636+
}
637+
638+
markdownString.appendMarkdown('\n\n');
639+
}
640+
641+
// Subject | Message
642+
markdownString.appendMarkdown(`${emojify(message.replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`);
643+
markdownString.appendMarkdown(`---\n\n`);
644+
645+
// Short stats
646+
if (shortStats) {
647+
markdownString.appendMarkdown(`<span>${shortStats.files === 1 ?
648+
l10n.t('{0} file changed', shortStats.files) :
649+
l10n.t('{0} files changed', shortStats.files)}</span>`);
650+
651+
if (shortStats.insertions) {
652+
markdownString.appendMarkdown(`,&nbsp;<span style="color:var(--vscode-scmGraph-historyItemHoverAdditionsForeground);">${shortStats.insertions === 1 ?
653+
l10n.t('{0} insertion{1}', shortStats.insertions, '(+)') :
654+
l10n.t('{0} insertions{1}', shortStats.insertions, '(+)')}</span>`);
655+
}
656+
657+
if (shortStats.deletions) {
658+
markdownString.appendMarkdown(`,&nbsp;<span style="color:var(--vscode-scmGraph-historyItemHoverDeletionsForeground);">${shortStats.deletions === 1 ?
659+
l10n.t('{0} deletion{1}', shortStats.deletions, '(-)') :
660+
l10n.t('{0} deletions{1}', shortStats.deletions, '(-)')}</span>`);
661+
}
662+
663+
markdownString.appendMarkdown(`\n\n---\n\n`);
664+
}
665+
666+
// Commands
667+
if (commands && commands.length > 0) {
668+
for (let index = 0; index < commands.length; index++) {
669+
if (index !== 0) {
670+
markdownString.appendMarkdown('&nbsp;&nbsp;|&nbsp;&nbsp;');
671+
}
672+
673+
const commandsMarkdown = commands[index]
674+
.map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify(command.arguments))} "${command.tooltip}")`);
675+
markdownString.appendMarkdown(commandsMarkdown.join('&nbsp;'));
676+
}
677+
}
678+
679+
// markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, hash, documentUri]))} "${l10n.t('Open Commit')}")`);
680+
// markdownString.appendMarkdown('&nbsp;');
681+
// markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`);
682+
683+
// // Remote hover commands
684+
// if (commands && commands.length > 0) {
685+
// markdownString.appendMarkdown('&nbsp;&nbsp;|&nbsp;&nbsp;');
686+
687+
// const remoteCommandsMarkdown = commands
688+
// .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`);
689+
// markdownString.appendMarkdown(remoteCommandsMarkdown.join('&nbsp;'));
690+
// }
691+
692+
// markdownString.appendMarkdown('&nbsp;&nbsp;|&nbsp;&nbsp;');
693+
// markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`);
694+
695+
return markdownString;
696+
}

β€Žextensions/git/src/timelineProvider.tsβ€Ž

Lines changed: 11 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode';
6+
import { CancellationToken, ConfigurationChangeEvent, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode';
77
import { Model } from './model';
88
import { Repository, Resource } from './repository';
99
import { debounce } from './decorators';
1010
import { emojify, ensureEmojis } from './emoji';
1111
import { CommandCenter } from './commands';
1212
import { OperationKind, OperationResult } from './operation';
1313
import { truncate } from './util';
14-
import { CommitShortStat } from './git';
1514
import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider';
1615
import { AvatarQuery, AvatarQueryCommit } from './api/git';
17-
18-
const AVATAR_SIZE = 20;
16+
import { getHistoryItemHover, getHistoryItemHoverCommitHashCommands, processHistoryItemRemoteHoverCommands } from './historyProvider';
1917

2018
export class GitTimelineItem extends TimelineItem {
2119
static is(item: TimelineItem): item is GitTimelineItem {
@@ -54,61 +52,6 @@ export class GitTimelineItem extends TimelineItem {
5452
return this.shortenRef(this.previousRef);
5553
}
5654

57-
setItemDetails(uri: Uri, hash: string | undefined, shortHash: string | undefined, avatar: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat, remoteSourceCommands: Command[] = []): void {
58-
this.tooltip = new MarkdownString('', true);
59-
this.tooltip.isTrusted = true;
60-
61-
const avatarMarkdown = avatar
62-
? `![${author}](${avatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})`
63-
: '$(account)';
64-
65-
if (email) {
66-
const emailTitle = l10n.t('Email');
67-
this.tooltip.appendMarkdown(`${avatarMarkdown} [**${author}**](mailto:${email} "${emailTitle} ${author}")`);
68-
} else {
69-
this.tooltip.appendMarkdown(`${avatarMarkdown} **${author}**`);
70-
}
71-
72-
this.tooltip.appendMarkdown(`, $(history) ${date}\n\n`);
73-
this.tooltip.appendMarkdown(`${message}\n\n`);
74-
75-
if (shortStat) {
76-
this.tooltip.appendMarkdown(`---\n\n`);
77-
78-
const labels: string[] = [];
79-
if (shortStat.insertions) {
80-
labels.push(`<span style="color:var(--vscode-scmGraph-historyItemHoverAdditionsForeground);">${shortStat.insertions === 1 ?
81-
l10n.t('{0} insertion{1}', shortStat.insertions, '(+)') :
82-
l10n.t('{0} insertions{1}', shortStat.insertions, '(+)')}</span>`);
83-
}
84-
85-
if (shortStat.deletions) {
86-
labels.push(`<span style="color:var(--vscode-scmGraph-historyItemHoverDeletionsForeground);">${shortStat.deletions === 1 ?
87-
l10n.t('{0} deletion{1}', shortStat.deletions, '(-)') :
88-
l10n.t('{0} deletions{1}', shortStat.deletions, '(-)')}</span>`);
89-
}
90-
91-
this.tooltip.appendMarkdown(`${labels.join(', ')}\n\n`);
92-
}
93-
94-
if (hash && shortHash) {
95-
this.tooltip.appendMarkdown(`---\n\n`);
96-
97-
this.tooltip.appendMarkdown(`[\`$(git-commit) ${shortHash} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash, uri]))} "${l10n.t('Open Commit')}")`);
98-
this.tooltip.appendMarkdown('&nbsp;');
99-
this.tooltip.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`);
100-
101-
// Remote commands
102-
if (remoteSourceCommands.length > 0) {
103-
this.tooltip.appendMarkdown('&nbsp;&nbsp;|&nbsp;&nbsp;');
104-
105-
const remoteCommandsMarkdown = remoteSourceCommands
106-
.map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`);
107-
this.tooltip.appendMarkdown(remoteCommandsMarkdown.join('&nbsp;'));
108-
}
109-
}
110-
}
111-
11255
private shortenRef(ref: string): string {
11356
if (ref === '' || ref === '~' || ref === 'HEAD') {
11457
return ref;
@@ -215,13 +158,10 @@ export class GitTimelineProvider implements TimelineProvider {
215158
commits.splice(commits.length - 1, 1);
216159
}
217160

218-
const dateFormatter = new Intl.DateTimeFormat(env.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
219-
220161
const config = workspace.getConfiguration('git', Uri.file(repo.root));
221162
const dateType = config.get<'committed' | 'authored'>('timeline.date');
222163
const showAuthor = config.get<boolean>('timeline.showAuthor');
223164
const showUncommitted = config.get<boolean>('timeline.showUncommitted');
224-
const commitShortHashLength = config.get<number>('commitShortHashLength') ?? 7;
225165

226166
const openComparison = l10n.t('Open Comparison');
227167

@@ -254,10 +194,15 @@ export class GitTimelineProvider implements TimelineProvider {
254194
item.description = c.authorName;
255195
}
256196

257-
const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands : [];
197+
const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands ?? [] : [];
258198
const messageWithLinks = await provideSourceControlHistoryItemMessageLinks(this.model, repo, message) ?? message;
259199

260-
item.setItemDetails(uri, c.hash, truncate(c.hash, commitShortHashLength, false), avatars?.get(c.hash), c.authorName!, c.authorEmail, dateFormatter.format(date), messageWithLinks, c.shortStat, commitRemoteSourceCommands);
200+
const commands: Command[][] = [
201+
getHistoryItemHoverCommitHashCommands(uri, c.hash),
202+
processHistoryItemRemoteHoverCommands(commitRemoteSourceCommands, c.hash)
203+
];
204+
205+
item.tooltip = getHistoryItemHover(avatars?.get(c.hash), c.authorName, c.authorEmail, date, messageWithLinks, c.shortStat, commands);
261206

262207
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
263208
if (cmd) {
@@ -282,7 +227,7 @@ export class GitTimelineProvider implements TimelineProvider {
282227
// TODO@eamodio: Replace with a better icon -- reflecting its status maybe?
283228
item.iconPath = new ThemeIcon('git-commit');
284229
item.description = '';
285-
item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type));
230+
item.tooltip = getHistoryItemHover(undefined, you, undefined, date, Resource.getStatusText(index.type), undefined, undefined);
286231

287232
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
288233
if (cmd) {
@@ -304,7 +249,7 @@ export class GitTimelineProvider implements TimelineProvider {
304249
const item = new GitTimelineItem('', index ? '~' : 'HEAD', l10n.t('Uncommitted Changes'), date.getTime(), 'working', 'git:file:working');
305250
item.iconPath = new ThemeIcon('circle-outline');
306251
item.description = '';
307-
item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type));
252+
item.tooltip = getHistoryItemHover(undefined, you, undefined, date, Resource.getStatusText(working.type), undefined, undefined);
308253

309254
const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri);
310255
if (cmd) {

0 commit comments

Comments
Β (0)