╭ changed ─────────────╮ │src/ui/Fork/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │204 }, │ │205 else => null, │ │206 }; │ │207 - if (text_box) |tab| { │ │208 - tab.options.border_style = if (selected_tab == id) .single else .hidden;│ │209 - tab.options.inverted = selected_tab == id; │ │210 - } │ │207 + if (text_box) |tab| ui.widget.markSelected(tab, selected_tab == id); │ │208 tabs_width += if (child.min_size) |min_size| min_size.width orelse 0 else 0;│ │209 } │ │210 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Home/Header.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │202 }, │ │203 else => null, │ │204 }; │ │205 - if (tb) |t| { │ │206 - const selected = self.getFocus().child_id == id; │ │207 - t.options.border_style = if (selected) .single else .hidden; │ │208 - t.options.inverted = selected; │ │209 - } │ │205 + if (tb) |t| ui.widget.markSelected(t, self.getFocus().child_id == id); │ │206 } │ │207 var scroll_constraint = constraint; │ │208 scroll_constraint.min_size.width = constraint.max_size.width orelse constraint.min_size.width;│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │622 const lb = self.listBox(); │ │623 for (lb.children.keys(), lb.children.values()) |id, *child| { │ │624 switch (child.widget) { │ │625 - .text_box => |*tb| { │ │626 - const selected = lb.getFocus().child_id == id; │ │627 - tb.options.border_style = if (selected) .single else .hidden; │ │628 - tb.options.inverted = selected; │ │629 - }, │ │625 + .text_box => |*tb| ui.widget.markSelected(tb, lb.getFocus().child_id == id),│ │626 else => {}, │ │627 } │ │628 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/ui/Repo/Diff.zig│ ╰────────────────────╯ ╭─────────────────────────────────────────────────────────────────╮ │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; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯