╭ changed ────╮ │build.zig.zon│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 3 .version = "0.0.0", │ │ 4 .dependencies = .{ │ │ 5 .xit = .{ │ │ 6 - .url = "git+https://github.com/xit-vcs/xit#6e9fadd5ab588713e55d2e04de356f6268c31c11",│ │ 7 - .hash = "xit-0.0.0-Ub3nDGIeVAHjs7mnOQ7nhvZth93VPbIENFyaiJgtBtjE", │ │ 6 + .url = "git+https://github.com/xit-vcs/xit#81dd696376f085645452483e310b2b56c68d4d30",│ │ 7 + .hash = "xit-0.0.0-Ub3nDE4mVAH69BI1gUvS9mXi8lZ3JQ9peYK8BBvoOn4g", │ │ 8 }, │ │ 9 }, │ │10 .paths = .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/embed/index.html│ ╰────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │ 5 <title>HAXY</title> │ │ 6 <style> │ │ 7 html { │ │ 8 - background-color: rgba(0, 50, 70, 1.0); │ │ 9 - color: rgba(200, 200, 200, 1.0); │ │ 8 + --bg: rgba(0, 50, 70, 1.0); │ │ 9 + --fg: rgba(200, 200, 200, 1.0); │ │10 + background-color: var(--bg); │ │11 + color: var(--fg); │ │12 } │ │13 │ │14 /* no body margin because it could push content past the viewport and│ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Fork/Header.zig│ ╰──────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │204 }, │ │205 else => null, │ │206 }; │ │207 - if (text_box) |tab| tab.options.border_style = if (selected_tab == id) .single else .hidden;│ │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 + } │ │211 tabs_width += if (child.min_size) |min_size| min_size.width orelse 0 else 0; │ │212 } │ │213 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Home/Header.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │203 else => null, │ │204 }; │ │205 if (tb) |t| { │ │206 - t.options.border_style = if (self.getFocus().child_id == id) .single else .hidden;│ │206 + const selected = self.getFocus().child_id == id; │ │207 + t.options.border_style = if (selected) .single else .hidden; │ │208 + t.options.inverted = selected; │ │209 } │ │210 } │ │211 var scroll_constraint = constraint; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ 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| tb.options.border_style = if (lb.getFocus().child_id == id) .single else .hidden,│ │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 + }, │ │630 else => {}, │ │631 } │ │632 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ───────────╮ │src/ui/Repo/Diff.zig│ ╰────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │207 if (data.window.start > 0) try addLink(allocator, box, "← previous", try data.route.link(session.page_arena, data.window.start -| page_size, data.path));│ │208 for (data.window.hunks) |hunk| { │ │209 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 addLink(allocator, box, hunk.text, ""); │ │210 + if (hunk.text.len != 0) try addHunk(allocator, box, hunk.text); │ │211 } │ │212 if (data.window.has_more) try addLink(allocator, box, "next →", try data.route.link(session.page_arena, data.window.start + page_size, data.path)); │ │213 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │214 │ │215 fn addLink(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), text: []const u8, link: []const u8) !void { │ │216 - var tb = try wgt.TextBox.init(allocator, text, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │216 + try addSpans(allocator, box, &.{.{ .text = text }}, link); │ │217 +} │ │218 + │ │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 { │ │224 + var spans: std.ArrayList(wgt.Span) = .empty; │ │225 + defer spans.deinit(allocator); │ │226 + var start: usize = 0; │ │227 + while (start < text.len) { │ │228 + // 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 ' '; │ │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 }); │ │240 + start = end; │ │241 + } │ │242 + try addSpans(allocator, box, spans.items, ""); │ │243 +} │ │244 + │ │245 +fn addSpans(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), spans: []const wgt.Span, link: []const u8) !void { │ │246 + var tb = try wgt.TextBox.initSpans(allocator, spans, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │247 errdefer tb.deinit(allocator); │ │248 tb.getFocus().mode = .all; │ │249 if (link.len != 0) tb.getFocus().kind = .{ .custom = link }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Events.zig│ ╰──────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │510 │ │511 const list = self.listBox(); │ │512 for (list.children.keys(), list.children.values()) |id, *child| switch (child.widget) { │ │513 - .text_box => |*text_box| text_box.options.border_style = if (list.getFocus().child_id == id) .single else .hidden,│ │513 + .text_box => |*text_box| { │ │514 + const selected = list.getFocus().child_id == id; │ │515 + text_box.options.border_style = if (selected) .single else .hidden; │ │516 + text_box.options.inverted = selected; │ │517 + }, │ │518 else => {}, │ │519 }; │ │520 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │720 .text_box => |*text_box| text_box, │ │721 else => continue, │ │722 }; │ │719 - tab.options.border_style = if (self.box.getFocus().child_id == id) .single else .hidden;│ │723 + const selected = self.box.getFocus().child_id == id; │ │724 + tab.options.border_style = if (selected) .single else .hidden; │ │725 + tab.options.inverted = selected; │ │726 } │ │727 try self.box.build(allocator, constraint, root_focus); │ │728 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │585 const lb = self.listBox(); │ │586 for (lb.children.keys(), lb.children.values()) |id, *child| { │ │587 switch (child.widget) { │ │588 - .text_box => |*tb| tb.options.border_style = if (lb.getFocus().child_id == id) .single else .hidden,│ │588 + .text_box => |*tb| { │ │589 + const selected = lb.getFocus().child_id == id; │ │590 + tb.options.border_style = if (selected) .single else .hidden; │ │591 + tb.options.inverted = selected; │ │592 + }, │ │593 else => {}, │ │594 } │ │595 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯