From 157567fd8f3fbfc09de0ca643d4b51ce28fb76df Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:21:34 +0100 Subject: [PATCH] Git - add git blame hover (status bar item, editor decoration) (#234338) --- extensions/git/src/blame.ts | 50 +++++++++++++++++++++++++++++++--- extensions/git/src/commands.ts | 11 +++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index d116922d9bf..10c8e0d98f2 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command } from 'vscode'; +import { DecorationOptions, l10n, Position, Range, TextEditor, TextEditorChange, TextEditorDecorationType, TextEditorChangeKind, ThemeColor, Uri, window, workspace, EventEmitter, ConfigurationChangeEvent, StatusBarItem, StatusBarAlignment, Command, MarkdownString } from 'vscode'; import { Model } from './model'; import { dispose, fromNow, IDisposable, pathEquals } from './util'; import { Repository } from './repository'; @@ -86,6 +86,41 @@ function processTextEditorChangesWithBlameInformation(blameInformation: BlameInf return changesWithBlameInformation; } +function getBlameInformationHover(documentUri: Uri, blameInformation: BlameInformation | string): MarkdownString { + if (typeof blameInformation === 'string') { + return new MarkdownString(blameInformation, true); + } + + const markdownString = new MarkdownString(); + markdownString.supportThemeIcons = true; + markdownString.isTrusted = true; + + if (blameInformation.authorName) { + markdownString.appendMarkdown(`$(account) **${blameInformation.authorName}**`); + + if (blameInformation.date) { + const dateString = new Date(blameInformation.date).toLocaleString(undefined, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); + markdownString.appendMarkdown(`, $(history) ${fromNow(blameInformation.date, true, true)} (${dateString})`); + } + + markdownString.appendMarkdown('\n\n'); + } + + markdownString.appendMarkdown(`${blameInformation.message}\n\n`); + markdownString.appendMarkdown(`---\n\n`); + + markdownString.appendMarkdown(`[$(eye) View Commit](command:git.blameStatusBarItem.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, blameInformation.id]))})`); + markdownString.appendMarkdown('  |  '); + markdownString.appendMarkdown(`[$(copy) ${blameInformation.id.substring(0, 8)}](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.id))})`); + + if (blameInformation.message) { + markdownString.appendMarkdown('  '); + markdownString.appendMarkdown(`[$(copy) Message](command:git.blameStatusBarItem.copyContent?${encodeURIComponent(JSON.stringify(blameInformation.message))})`); + } + + return markdownString; +} + interface RepositoryBlameInformation { readonly commit: string; /* commit used for blame information */ readonly blameInformation: Map; @@ -314,16 +349,19 @@ class GitBlameEditorDecoration { const contentText = typeof blame.blameInformation === 'string' ? blame.blameInformation : `${blame.blameInformation.message ?? ''}, ${blame.blameInformation.authorName ?? ''} (${fromNow(blame.blameInformation.date ?? Date.now(), true, true)})`; - return this._createDecoration(blame.lineNumber, contentText); + const hoverMessage = getBlameInformationHover(textEditor.document.uri, blame.blameInformation); + + return this._createDecoration(blame.lineNumber, contentText, hoverMessage); }); textEditor.setDecorations(this._decorationType, decorations); } - private _createDecoration(lineNumber: number, contentText: string): DecorationOptions { + private _createDecoration(lineNumber: number, contentText: string, hoverMessage: MarkdownString): DecorationOptions { const position = new Position(lineNumber, Number.MAX_SAFE_INTEGER); return { + hoverMessage, range: new Range(position, position), renderOptions: { after: { @@ -389,6 +427,7 @@ class GitBlameStatusBarItem { if (!this._statusBarItem) { this._statusBarItem = window.createStatusBarItem('git.blame', StatusBarAlignment.Right, 200); + this._statusBarItem.name = l10n.t('Git Blame Information'); this._disposables.push(this._statusBarItem); } @@ -400,11 +439,14 @@ class GitBlameStatusBarItem { if (typeof blameInformation[0].blameInformation === 'string') { this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation}`; + this._statusBarItem.tooltip = getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation); + this._statusBarItem.command = undefined; } else { this._statusBarItem.text = `$(git-commit) ${blameInformation[0].blameInformation.authorName ?? ''} (${fromNow(blameInformation[0].blameInformation.date ?? new Date(), true, true)})`; + this._statusBarItem.tooltip = getBlameInformationHover(textEditor.document.uri, blameInformation[0].blameInformation); this._statusBarItem.command = { title: l10n.t('View Commit'), - command: 'git.statusBar.viewCommit', + command: 'git.blameStatusBarItem.viewCommit', arguments: [textEditor.document.uri, blameInformation[0].blameInformation.id] } satisfies Command; } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 84d9b30f7b7..43518a77843 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -4307,7 +4307,7 @@ export class CommandCenter { env.clipboard.writeText(historyItem.message); } - @command('git.statusBar.viewCommit', { repository: true }) + @command('git.blameStatusBarItem.viewCommit', { repository: true }) async viewStatusBarCommit(repository: Repository, historyItemId: string): Promise { if (!repository || !historyItemId) { return; @@ -4325,6 +4325,15 @@ export class CommandCenter { await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); } + @command('git.blameStatusBarItem.copyContent') + async blameStatusBarCopyContent(content: string): Promise { + if (typeof content !== 'string') { + return; + } + + env.clipboard.writeText(content); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise;