feat: add git blame with inline display (wip)

This commit is contained in:
Miguel Granero 2026-01-23 21:36:14 +01:00 committed by CJ van den Berg
parent 3402a54a2e
commit 90a817066a
7 changed files with 223 additions and 2 deletions

View file

@ -31,6 +31,11 @@ pub const Metrics = struct {
pub const egc_last_func = *const fn (self: Metrics, egcs: []const u8) []const u8;
};
pub const BlameLine = struct {
author_name: []const u8,
author_stamp: usize,
};
arena: std.heap.ArenaAllocator,
allocator: Allocator,
external_allocator: Allocator,
@ -50,6 +55,8 @@ meta: ?[]const u8 = null,
lsp_version: usize = 1,
vcs_id: ?[]const u8 = null,
vcs_content: ?ArrayList(u8) = null,
vcs_blame: ?ArrayList(u8) = null,
vcs_blame_items: ArrayList(BlameLine) = .empty,
last_view: ?usize = null,
undo_head: ?*UndoNode = null,
@ -1212,6 +1219,8 @@ pub fn create(allocator: Allocator) error{OutOfMemory}!*Self {
pub fn deinit(self: *Self) void {
self.clear_vcs_content();
self.clear_vcs_blame();
self.vcs_blame_items.deinit(self.allocator);
if (self.vcs_id) |buf| self.external_allocator.free(buf);
if (self.meta) |buf| self.external_allocator.free(buf);
if (self.file_buf) |buf| self.external_allocator.free(buf);
@ -1257,6 +1266,7 @@ pub fn set_vcs_id(self: *Self, vcs_id: []const u8) error{OutOfMemory}!bool {
self.external_allocator.free(old_id);
}
self.clear_vcs_content();
self.clear_vcs_blame();
self.vcs_id = try self.external_allocator.dupe(u8, vcs_id);
return true;
}
@ -1287,6 +1297,92 @@ pub fn get_vcs_content(self: *const Self) ?[]const u8 {
return if (self.vcs_content) |*buf| buf.items else null;
}
pub fn clear_vcs_blame(self: *Self) void {
if (self.vcs_blame) |*buf| {
buf.deinit(self.external_allocator);
self.vcs_blame = null;
}
}
pub fn get_vcs_blame(self: *const Self) ?[]const u8 {
return if (self.vcs_blame) |*buf| buf.items else null;
}
pub fn set_vcs_blame(self: *Self, vcs_blame: []const u8) error{OutOfMemory}!void {
if (self.vcs_blame) |*al| {
try al.appendSlice(self.external_allocator, vcs_blame);
} else {
var al: ArrayList(u8) = .empty;
try al.appendSlice(self.external_allocator, vcs_blame);
self.vcs_blame = al;
}
}
/// Can probably be optimized
pub fn parse_git_blame(self: *Self) !void {
self.vcs_blame_items.clearAndFree(self.allocator);
var stream = std.io.fixedBufferStream(self.vcs_blame.?.items);
var reader = stream.reader();
var chunk_active = false;
var current_committer: []u8 = "";
var current_stamp: usize = undefined;
var buffer: [200]u8 = undefined;
while (true) {
const line = reader.readUntilDelimiter(&buffer, '\n') catch {
break;
};
if (line.len == 0)
continue;
if (line.len >= 40 and std.ascii.isHex(line[0])) {
if (chunk_active)
break; // error
chunk_active = true;
var it = std.mem.splitScalar(u8, line, ' ');
const sha = it.next() orelse continue;
if (sha.len != 40) continue;
_ = it.next(); // original line (ignored)
const number = try std.fmt.parseInt(
i64,
it.next() orelse break,
10,
);
if (number != self.vcs_blame_items.items.len + 1)
break;
} else if (std.mem.startsWith(u8, line, "author ")) {
if (!chunk_active)
break; // error
current_committer = try self.allocator.dupe(
u8,
line["author ".len..],
);
} else if (std.mem.startsWith(u8, line, "author-time ")) {
if (!chunk_active)
break; // error
current_stamp = try std.fmt.parseInt(
usize,
line["author-time ".len..],
10,
);
const ptr = try self.vcs_blame_items.addOne(self.allocator);
ptr.* = BlameLine{ .author_name = current_committer, .author_stamp = current_stamp };
chunk_active = false;
}
}
}
pub fn get_line_blame(self: *const Self, line: usize) ?[]const u8 {
if (line + 1 > self.vcs_blame_items.items.len)
return null;
return self.vcs_blame_items.items[line].author_name;
}
pub fn update_last_used_time(self: *Self) void {
self.utime = std.time.milliTimestamp();
}