Skip to content

Commit 157567f

Browse files
authored
Git - add git blame hover (status bar item, editor decoration) (microsoft#234338)
1 parent 7bb46ec commit 157567f

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

extensions/git/src/blame.ts

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

6-
import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command } from 'vscode';
6+
import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString } from 'vscode';
77
import { Model } from './model';
88
import { dispose, fromNow, IDisposable, pathEquals } from './util';
99
import { Repository } from './repository';
@@ -86,6 +86,41 @@ function processTextEditorChangesWithBlameInformation(blameInformation: BlameInf
8686
return changesWithBlameInformation;
8787
}
8888

89+
function getBlameInformationHover(documentUri: Uri, blameInformation: BlameInformation | string): MarkdownString {
90+
if (typeof blameInformation === 'string') {
91+
return new MarkdownString(blameInformation, true);
92+
}
93+
94+
const markdownString = new MarkdownString();
95+
markdownString.supportThemeIcons = true;
96+
markdownString.isTrusted = true;
97+
98+
if (blameInformation.authorName) {
99+
markdownString.appendMarkdown(`$(account) **${blameInformation.authorName}**`);
100+
101+
if (blameInformation.date) {
102+
const dateString = new Date(blameInformation.date).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
103+
markdownString.appendMarkdown(`, $(history) ${fromNow(blameInformation.date, true, true)} (${dateString})`);
104+
}
105+
106+
markdownString.appendMarkdown('\n\n');
107+
}
108+
109+
markdownString.appendMarkdown(`${blameInformation.message}\n\n`);
110+
markdownString.appendMarkdown(`---\n\n`);
111+
112+
markdownString.appendMarkdown(`[$(eye) View Commit](command:git.blameStatusBarItem.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformation.id]))})`);
113+
markdownString.appendMarkdown('  |  ');
114+
markdownString.appendMarkdown(`[$(copy) ${blameInformation.id.substring(0, 8)}](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.id))})`);
115+
116+
if (blameInformation.message) {
117+
markdownString.appendMarkdown('  ');
118+
markdownString.appendMarkdown(`[$(copy) Message](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.message))})`);
119+
}
120+
121+
return markdownString;
122+
}
123+
89124
interface RepositoryBlameInformation {
90125
readonly commit: string; /* commit used for blame information */
91126
readonly blameInformation: Map<Uri, ResourceBlameInformation>;
@@ -314,16 +349,19 @@ class GitBlameEditorDecoration {
314349
const contentText = typeof blame.blameInformation === 'string'
315350
? blame.blameInformation
316351
: `${blame.blameInformation.message ?? ''}, ${blame.blameInformation.authorName ?? ''} (${fromNow(blame.blameInformation.date ?? Date.now(), true, true)})`;
317-
return this._createDecoration(blame.lineNumber, contentText);
352+
const hoverMessage = getBlameInformationHover(textEditor.document.uri, blame.blameInformation);
353+
354+
return this._createDecoration(blame.lineNumber, contentText, hoverMessage);
318355
});
319356

320357
textEditor.setDecorations(this._decorationType, decorations);
321358
}
322359

323-
private _createDecoration(lineNumber: number, contentText: string): DecorationOptions {
360+
private _createDecoration(lineNumber: number, contentText: string, hoverMessage: MarkdownString): DecorationOptions {
324361
const position = new Position(lineNumber, Number.MAX_SAFE_INTEGER);
325362

326363
return {
364+
hoverMessage,
327365
range: new Range(position, position),
328366
renderOptions: {
329367
after: {
@@ -389,6 +427,7 @@ class GitBlameStatusBarItem {
389427

390428
if (!this._statusBarItem) {
391429
this._statusBarItem = window.createStatusBarItem('git.blame', StatusBarAlignment.Right, 200);
430+
this._statusBarItem.name = l10n.t('Git Blame Information');
392431
this._disposables.push(this._statusBarItem);
393432
}
394433

@@ -400,11 +439,14 @@ class GitBlameStatusBarItem {
400439

401440
if (typeof blameInformation[0].blameInformation === 'string') {
402441
this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation}`;
442+
this._statusBarItem.tooltip = getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation);
443+
this._statusBarItem.command = undefined;
403444
} else {
404445
this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation.authorName ?? ''} (${fromNow(blameInformation[0].blameInformation.date ?? new Date(), true, true)})`;
446+
this._statusBarItem.tooltip = getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation);
405447
this._statusBarItem.command = {
406448
title: l10n.t('View Commit'),
407-
command: 'git.statusBar.viewCommit',
449+
command: 'git.blameStatusBarItem.viewCommit',
408450
arguments: [textEditor.document.uri, blameInformation[0].blameInformation.id]
409451
} satisfies Command;
410452
}

extensions/git/src/commands.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4307,7 +4307,7 @@ export class CommandCenter {
43074307
env.clipboard.writeText(historyItem.message);
43084308
}
43094309

4310-
@command('git.statusBar.viewCommit', { repository: true })
4310+
@command('git.blameStatusBarItem.viewCommit', { repository: true })
43114311
async viewStatusBarCommit(repository: Repository, historyItemId: string): Promise<void> {
43124312
if (!repository || !historyItemId) {
43134313
return;
@@ -4325,6 +4325,15 @@ export class CommandCenter {
43254325
await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources });
43264326
}
43274327

4328+
@command('git.blameStatusBarItem.copyContent')
4329+
async blameStatusBarCopyContent(content: string): Promise<void> {
4330+
if (typeof content !== 'string') {
4331+
return;
4332+
}
4333+
4334+
env.clipboard.writeText(content);
4335+
}
4336+
43284337
private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any {
43294338
const result = (...args: any[]) => {
43304339
let result: Promise<any>;

0 commit comments

Comments
 (0)