╭ changed ───────────╮ │src/ui/Repo/Diff.zig│ ╰────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────╮ │14 pub const Hunk = struct { │ │15 path: ?[]const u8 = null, │ │16 text: []const u8, │ │17 + // one byte per line of text: the edit's +/-/space prefix│ │18 + prefixes: []const u8, │ │19 }; │ │20 pub const Window = struct { │ │21 hunks: []const Hunk = &.{}, │ ╰─────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │145 has_more = true; │ │146 break :file_loop; │ │147 } │ │148 + const rendered = try renderHunk(repo_kind, repo_opts, arena, &hunk_iter, &hunk);│ │149 try hunks.append(arena, .{ │ │150 .path = if (path_attached) null else try arena.dupe(u8, pair.path), │ │148 - .text = try renderHunk(repo_kind, repo_opts, arena, &hunk_iter, &hunk), │ │151 + .text = rendered.text, │ │152 + .prefixes = rendered.prefixes, │ │153 }); │ │154 path_attached = true; │ │155 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │158 return .{ .hunks = try hunks.toOwnedSlice(arena), .start = start, .has_more = has_more };│ │159 } │ │160 │ │157 -// render edits with right-aligned line numbers │ │161 +// render edits with right-aligned line numbers, alongside each line's prefix │ │162 fn renderHunk( │ │163 comptime repo_kind: rp.RepoKind, │ │164 comptime repo_opts: rp.RepoOpts(repo_kind), │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │165 arena: std.mem.Allocator, │ │166 hunk_iter: *df.HunkIterator(repo_kind, repo_opts),│ │167 hunk: *df.Hunk(repo_kind, repo_opts), │ │164 -) ![]const u8 { │ │168 +) !struct { text: []const u8, prefixes: []const u8 } {│ │169 var out: std.Io.Writer.Allocating = .init(arena); │ │170 defer out.deinit(); │ │171 + var prefixes: std.ArrayList(u8) = .empty; │ │172 var max_num: usize = 1; │ │173 for (hunk.edits.items) |edit| { │ │174 const n = editLineNum(edit) + 1; │ ╰───────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │190 const num = editLineNum(edit) + 1; │ │191 try out.writer.splatByteAll(' ', width - std.fmt.count("{d}", .{num})); │ │192 try out.writer.print("{d} {c}{s}", .{ num, prefix, text }); │ │193 + try prefixes.append(arena, prefix); │ │194 } │ │189 - return out.toOwnedSlice(); │ │195 + return .{ .text = try out.toOwnedSlice(), .prefixes = try prefixes.toOwnedSlice(arena) };│ │196 } │ │197 │ │198 // the line number to show for an edit: the new-side number for kept and inserted │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │213 if (data.window.start > 0) try addLink(allocator, box, "← previous", try data.route.link(session.page_arena, data.window.start -| page_size, data.path));│ │214 for (data.window.hunks) |hunk| { │ │215 if (data.path.len == 0) if (hunk.path) |path| try addLink(allocator, box, path, try data.route.link(session.page_arena, 0, path)); │ │210 - if (hunk.text.len != 0) try addHunk(allocator, box, hunk.text); │ │216 + if (hunk.text.len != 0) try addHunk(allocator, box, hunk); │ │217 } │ │218 if (data.window.has_more) try addLink(allocator, box, "next →", try data.route.link(session.page_arena, data.window.start + page_size, data.path)); │ │219 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │222 try addSpans(allocator, box, &.{.{ .text = text }}, link); │ │223 } │ │224 │ │219 -// a hunk rendered by renderHunk: each line is a right-aligned line number, a │ │220 -// space, then the edit's +/-/space prefix. inserted lines are green and │ │221 -// deleted lines red. the text box copies the text, so the spans only live │ │222 -// until init. │ │223 -fn addHunk(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), text: []const u8) !void {│ │225 +// one span per line, inserted lines green and deleted lines red. the text │ │226 +// box copies the text, so the spans only live until init. │ │227 +fn addHunk(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), hunk: Hunk) !void { │ │228 var spans: std.ArrayList(wgt.Span) = .empty; │ │229 defer spans.deinit(allocator); │ │230 var start: usize = 0; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │227 - while (start < text.len) { │ │231 + for (hunk.prefixes) |prefix| { │ │232 // each span keeps its trailing newline so the text box still breaks there │ │229 - const end = if (std.mem.indexOfScalarPos(u8, text, start, '\n')) |nl| nl + 1 else text.len; │ │230 - const line = text[start..end]; │ │231 - const num = std.mem.trimStart(u8, line, " "); │ │232 - const digits = std.mem.indexOfNone(u8, num, "0123456789") orelse num.len; │ │233 - const prefix: u8 = if (digits + 1 < num.len) num[digits + 1] else ' '; │ │233 + const end = if (std.mem.indexOfScalarPos(u8, hunk.text, start, '\n')) |nl| nl + 1 else hunk.text.len;│ │234 const style: wgt.Style = switch (prefix) { │ │235 '+' => .{ .fg = .{ .ansi = .green } }, │ │236 '-' => .{ .fg = .{ .ansi = .red } }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │237 else => .{}, │ │238 }; │ │239 - try spans.append(allocator, .{ .text = line, .style = style }); │ │239 + try spans.append(allocator, .{ .text = hunk.text[start..end], .style = style });│ │240 start = end; │ │241 } │ │242 try addSpans(allocator, box, spans.items, ""); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯