╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Diff.zig ─────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭ find file ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ ╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╮ │ │ │http│ ssh │ │ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─╯
293 '+' => .{ .fg = .{ .ansi = .green } }, 294 '-' => .{ .fg = .{ .ansi = .red } }, 295 else => .{}, 296 }; 297 try spans.append(allocator, .{ .text = hunk.text[start..end], .style = style }); 298 start = end; 299 } 300 try addSpans(allocator, box, spans.items, "", "", .single); 301 } 302 303 fn addSpans(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), spans: []const wgt.Span, link: []const u8, label: []const u8, border: wgt.Border) !void { 304 var tb = try wgt.TextBox.initSpans(allocator, spans, .{ .border = border, .round_corners = true, .wrap_kind = .none, .top_label = .{ .text = label } }); 305 errdefer tb.deinit(allocator); 306 tb.getFocus().mode = .all; 307 if (link.len != 0) tb.getFocus().kind = .{ .custom = link }; 308 try box.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null, .flex = .shrink }); 309 } 310 311 pub const View = struct { 312 scroll: wgt.Scroll(ui.Widget), 313 314 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { 315 var view: View = blk: { 316 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .vert }); 317 errdefer box.deinit(allocator); 318 break :blk .{ .scroll = try wgt.Scroll(ui.Widget).init(allocator, .{ .box = box }, .{ .direction = .both, .web_native = !session.is_terminal, .fill = true }) }; 319 }; 320 errdefer view.deinit(allocator); 321 const box = view.inner(); 322 try data.appendWindow(allocator, session, box); 323 if (box.children.count() == 0) try addLink(allocator, box, "no changes", ""); 324 box.getFocus().child_id = box.children.keys()[0]; 325 return view; 326 } 327 328 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 329 self.scroll.deinit(allocator); 330 } 331 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 332 self.clearGrid(); 333 try self.scroll.build(allocator, constraint, root_focus); 334 } 335 pub fn clearGrid(self: *View) void { 336 self.scroll.clearGrid(); 337 } 338 pub fn getGrid(self: View) ?Grid { 339 return self.scroll.getGrid(); 340 } 341 pub fn getFocus(self: *View) *Focus { 342 return self.scroll.getFocus(); 343 } 344 pub fn atTop(self: *View) bool { 345 const box = self.inner(); 346 return self.scroll.y == 0 and (box.children.count() == 0 or box.getFocus().child_id == box.children.keys()[0]); 347 } 348 fn inner(self: *View) *wgt.Box(ui.Widget) { 349 return &self.scroll.child.box; 350 } 351 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 352 _ = allocator; 353 const sc = &self.scroll; 354 const box = self.inner(); 355 if (inp.rowDelta(key, @intCast(box.children.count()))) |delta| return ui.widget.moveRowFocus(box, sc, root_focus, delta); 356 switch (key) { 357 // scroll horizontally within the diff 358 .arrow_left => if (!ui.widget.moveInSelectedRow(box, root_focus, false) and sc.x > 0) { 359 sc.x -= 1; 360 sc.clampToContent(); 361 }, 362 .arrow_right => if (!ui.widget.moveInSelectedRow(box, root_focus, true)) { 363 sc.x += 1; 364 sc.clampToContent(); 365 }, 366 // Enter / a click on a "previous"/"next" row follow its "a:" link in 367 // the host (it reloads the adjacent window), so they don't reach here. 368 else => {}, 369 } 370 } 371 }; 372