Something went wrong. Try again.
A history browser for git, a replacement for gitk, drawn with Vulkan on Wayland.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The file list: the files the selected commit changes, under an entry for//! its details. Clicking one scrolls the diff to it, and the one the diff//! is showing is marked.
const std = @import("std");const aerie = @import("aerie");const Canvas = aerie.gpu.Canvas;const Rect = Canvas.Rect;
const Change = @import("../history/Change.zig");const Text = @import("Text.zig");const theme = @import("theme.zig");const Scrollbar = @import("Scrollbar.zig");const l10n = @import("../l10n.zig");
const FileList = @This();
rect: Rect = .{ .x = 0, .y = 0, .width = 0, .height = 0 },/// The scrollbar's track, beside the rows.vtrack: Rect = .{ .x = 0, .y = 0, .width = 0, .height = 0 },row_height: f32 = 1,scroll: f32 = 0,
/// What a row is: the details, or a file by its index.pub const Entry = union(enum) { details, file: usize,};
pub fn count(files: usize) usize { return files + 1;}
pub fn entryAt(l: *const FileList, y: f32, files: usize) ?Entry { if (y < l.rect.y or y >= l.rect.bottom()) return null; const row: usize = @intFromFloat(@floor((y - l.rect.y + l.scroll) / l.row_height)); if (row >= count(files)) return null; return if (row == 0) .details else .{ .file = row - 1 };}
pub fn scrollbar(l: *const FileList, files: usize) Scrollbar { return .{ .track = l.vtrack, .axis = .vertical, .content = @as(f32, @floatFromInt(count(files))) * l.row_height, .view = l.rect.height, .offset = l.scroll };}
pub fn scrollBy(l: *FileList, dy: f32, files: usize) bool { const before = l.scroll; l.scroll = std.math.clamp(l.scroll + dy, 0, l.scrollbar(files).maxOffset()); return l.scroll != before;}
/// The letter `gitk` and `git status` give each kind of change.fn letter(s: Change.Status) []const u8 { return switch (s) { .added => "A", .deleted => "D", .modified => "M", .renamed => "R", .copied => "C", .type_changed => "T", .unmerged => "U", .unknown => "?", };}
/// Draws the list. `current` is the entry the diff is showing.pub fn draw(l: *const FileList, canvas: *Canvas, text: *Text, files: []const Change.File, current: ?Entry, dragging: bool, scale: f32) !void { try l.scrollbar(files.len).draw(canvas, dragging, scale); try canvas.rect(l.rect.x, l.rect.y, l.rect.width, l.rect.height, theme.current.list_background); const saved = canvas.pushClip(l.rect); defer canvas.setClip(saved); const pad = @round(6 * scale); const letter_w = @round(16 * scale); const n = count(files.len); const first: usize = @intFromFloat(@floor(l.scroll / l.row_height)); const end = @min(n, first + @as(usize, @intFromFloat(@ceil(l.rect.height / l.row_height))) + 1); for (first..end) |row| { const top = l.rect.y + @as(f32, @floatFromInt(row)) * l.row_height - l.scroll; const entry: Entry = if (row == 0) .details else .{ .file = row - 1 }; const is_current = if (current) |c| std.meta.eql(c, entry) else false; if (is_current) try canvas.rect(l.rect.x, top, l.rect.width, l.row_height, theme.current.selection_background); const fg = if (is_current) theme.current.selection_foreground else theme.current.foreground; const dim = if (is_current) theme.current.selection_foreground else theme.current.dim_foreground; const baseline = top + text.baselineIn(.ui, l.row_height); switch (entry) { .details => _ = try text.draw(canvas, .ui, l10n.text("files-details"), l.rect.x + pad, baseline, dim), .file => |i| { const f = files[i]; _ = try text.draw(canvas, .mono, letter(f.status), l.rect.x + pad, top + text.baselineIn(.mono, l.row_height), dim); _ = try text.draw(canvas, .ui, f.path, l.rect.x + pad + letter_w, baseline, fg); }, } }}