From 53c8d1cf0aa44d68eed2c2514cbeec9ab20a5170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Thom=C3=A9?= Date: Sat, 16 Sep 2023 21:36:38 +0200 Subject: [PATCH] Call `git blame` directly and parse porcelain --- src/extension.ts | 100 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 67d4588..5fd504d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,44 @@ import * as vscode from "vscode"; -import { GitExtension, Repository, API } from "./git"; +import * as cp from "child_process"; + +function parseGitBlamePorcelain(blame: string): { [key: string]: string } { + const fields = Object.fromEntries( + blame + .trim() + .split("\n") + .map((line: string) => { + const words = line.split(" "); + const key = words[0]; + const value = words.slice(1).join(" "); + return [key, value]; + }) + ); + return fields; +} + +function relativeTimePassed(now: number, past: number): string { + var msMinutes = 60 * 1000; + var msHours = msMinutes * 60; + var msDays = msHours * 24; + var msMonths = msDays * 30; + var msYears = msDays * 365; + + var diff = now - past; + + if (diff < msMinutes) { + return Math.round(diff / 1000) + " seconds ago"; + } else if (diff < msHours) { + return Math.round(diff / msMinutes) + " minutes ago"; + } else if (diff < msDays) { + return Math.round(diff / msHours) + " hours ago"; + } else if (diff < msMonths) { + return "Around " + Math.round(diff / msDays) + " days ago"; + } else if (diff < msYears) { + return "Around " + Math.round(diff / msMonths) + " months ago"; + } else { + return "Around " + Math.round(diff / msYears) + " years ago"; + } +} const annotationDecoration: vscode.TextEditorDecorationType = vscode.window.createTextEditorDecorationType({ @@ -25,33 +64,37 @@ export function activate(context: vscode.ExtensionContext) { activeEditor.selection.active.line ); - const gitExtension = - vscode.extensions.getExtension("vscode.git"); - if (gitExtension === undefined) { - vscode.window.showErrorMessage("Unable to load git extension"); - return; - } - const git = gitExtension.exports.getAPI(1); - const repo = git.getRepository(activeEditor.document.uri); + const file = activeEditor.document.uri; + const line = activeLine.lineNumber; + const command = "git"; + const args = ["blame", "--porcelain", `-L${line},+1`, file.fsPath]; + const workspaceFolder = + vscode.workspace.getWorkspaceFolder(file)?.uri.path; + const options = { cwd: workspaceFolder }; + const cmd = cp.spawn(command, args, options); - if (repo === null) { - vscode.window.showErrorMessage("Unable to get git repository"); - return; - } + cmd.stdout.on("data", (data) => { + const blame = data.toString(); + + const fields = parseGitBlamePorcelain(blame); - repo.blame(activeEditor.document.uri.path).then((blame) => { - const range = activeEditor.document.validateRange( - new vscode.Range( - activeLine.lineNumber, - activeLine.text.length, - activeLine.lineNumber, - 0 - ) + const range = new vscode.Range( + activeLine.lineNumber, + activeLine.text.length, + activeLine.lineNumber, + 0 ); - // TODO Pretty format and only use blame for selected line. - // activeLine.text - const message = blame; + const elapsed = relativeTimePassed( + Date.now(), + parseInt(fields["author-time"]) + ); + + // TODO If same username, write "You" instead of username. + // TODO If dirty, set summary to "Uncommitted changes". + // TODO If close in time, set summary to "now". + const message = `${fields.author}, ${elapsed} • ${fields.summary}`; + const hoverMessage = JSON.stringify(fields, null, 2); const renderOptions = { after: { @@ -60,12 +103,16 @@ export function activate(context: vscode.ExtensionContext) { contentText: message, fontWeight: "normal", fontStyle: "normal", - textDecoration: "none; opacity: 0.5; position: absolute;", + textDecoration: "none; opacity: 0.25; position: absolute;", }, }; const decorations = [ - { range: range, hoverMessage: message, renderOptions: renderOptions }, + { + range: range, + hoverMessage: hoverMessage, + renderOptions: renderOptions, + }, ]; activeEditor.setDecorations(annotationDecoration, decorations); @@ -74,5 +121,4 @@ export function activate(context: vscode.ExtensionContext) { ); } -// This method is called when your extension is deactivated export function deactivate() {}