╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │18 // how many diff hunks one window of a commit's diff shows; "next"/"previous"│ │19 // move to the adjacent window. │ │20 const diff_page = 10; │ │21 +// the largest commit message the detail view will read in full. │ │22 +const max_message_size = 64 * 1024; │ │23 │ │24 pub const Hunk = struct { │ │25 path: ?[]const u8 = null, // the file this hunk belongs to │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────╮ │31 pub const Commit = struct { │ │32 oid: []const u8, │ │33 date: []const u8, // "YYYY-MM-DD" │ │32 - message: []const u8, // first line only │ │34 + message: []const u8, // trimmed, may be multi-line │ │35 hunks: []const Hunk, │ │36 // the hunk index this window starts at (0 = the first window).│ │37 window_start: usize, │ ╰───────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │109 buf[count] = .{ │ │110 .oid = try aa.dupe(u8, &commit_object.oid), │ │111 .date = try formatDate(aa, md.timestamp), │ │110 - .message = try aa.dupe(u8, firstLine(md.message orelse "")), │ │112 + .message = try readMessage(repo_kind, repo_opts, aa, commit_object),│ │113 .hunks = &.{}, │ │114 .window_start = 0, │ │115 .has_prev = false, │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │301 }); │ │302 } │ │303 │ │302 -// the first line of a commit message, with surrounding whitespace trimmed. │ │304 +// the whole commit message, trimmed and duped into `aa`. an oversized │ │305 +// message falls back to that first line. │ │306 +fn readMessage( │ │307 + comptime repo_kind: rp.RepoKind, │ │308 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │309 + aa: std.mem.Allocator, │ │310 + commit_object: *xit.object.Object(repo_kind, repo_opts), │ │311 +) ![]const u8 { │ │312 + const md = commit_object.content.commit.metadata; │ │313 + try commit_object.object_reader.seekTo(commit_object.content.commit.message_position); │ │314 + const message = commit_object.object_reader.interface.allocRemaining(aa, .limited(max_message_size)) catch│ │315 + return try aa.dupe(u8, std.mem.trim(u8, md.message orelse "", " \t\r\n")); │ │316 + return std.mem.trim(u8, message, " \t\r\n"); │ │317 +} │ │318 + │ │319 +// the first line of a commit message, for the one-row list entries. │ │320 fn firstLine(message: []const u8) []const u8 { │ │304 - const trimmed = std.mem.trim(u8, message, " \t\r\n"); │ │305 - const nl = std.mem.indexOfScalar(u8, trimmed, '\n'); │ │306 - return if (nl) |i| trimmed[0..i] else trimmed; │ │321 + const nl = std.mem.indexOfScalar(u8, message, '\n'); │ │322 + return if (nl) |i| std.mem.trimEnd(u8, message[0..i], " \t\r") else message; │ │323 } │ │324 │ │325 pub const View = struct { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────╮ │331 session: *ui.Session, │ │332 // the commit whose diff the pane currently shows (index into data.commits).│ │333 diffed_index: ?usize, │ │334 + // the diff pane's message row, capped to the pane width so it word-wraps. │ │335 + message_id: ?usize, │ │336 │ │337 const header_index: usize = 0; │ │338 const content_index: usize = 1; │ ╰─────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │365 // an in-page "ai:" anchor so a commit row is clickable with │ │366 // js off (the browser follows it, rooting the list there); │ │367 // with wasm the click just selects it and swaps the diff pane. │ │350 - try addRow(allocator, &list_box, commit.message, try commitRowLink(session.page_arena, data.identity, commit.oid, "")); │ │368 + try addRow(allocator, &list_box, firstLine(commit.message), try commitRowLink(session.page_arena, data.identity, commit.oid, ""));│ │369 } │ │370 if (data.next_start) |next| { │ │371 try addRow(allocator, &list_box, "next →", try commitsLink(session.page_arena, data.identity, next, 0, "")); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────╮ │413 .data = data, │ │414 .session = session, │ │415 .diffed_index = null,│ │416 + .message_id = null, │ │417 }; │ │418 } │ │419 │ ╰──────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │426 } │ │427 │ │428 // one hunk as a focusable multi-line text box. its lines sit flush (the │ │410 - // inner Text rows have no border); the box's hidden border reserves the │ │411 - // space the double border occupies when focused, so focusing doesn't shift │ │412 - // layout. `lines` are joined into the page arena (text_box borrows it). │ │429 + // inner Text rows have no border). `lines` are joined into the page arena │ │430 + // (text_box borrows it). │ │431 fn addHunkBox(self: *View, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), lines: []const []const u8) !void {│ │432 if (lines.len == 0) return; │ │433 const text = try std.mem.join(self.session.page_arena.allocator(), "\n", lines); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │416 - var tb = try wgt.TextBox(ui.Widget).init(allocator, text, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .none });│ │434 + var tb = try wgt.TextBox(ui.Widget).init(allocator, text, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │435 errdefer tb.deinit(allocator); │ │436 tb.getFocus().focusable = true; │ │437 try box.children.put(allocator, tb.getFocus().id, .{ .widget = .{ .text_box = tb }, .rect = null, .min_size = null }); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │442 // there. │ │443 fn addPathBox(self: *View, allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), path: []const u8, oid: []const u8) !void { │ │444 const link = try commitsLink(self.session.page_arena, self.data.identity, oid, 0, path); │ │427 - var tb = try wgt.TextBox(ui.Widget).init(allocator, path, .{ .border_style = .hidden, .rounded_corners = true, .wrap_kind = .none });│ │445 + var tb = try wgt.TextBox(ui.Widget).init(allocator, path, .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ │446 errdefer tb.deinit(allocator); │ │447 tb.getFocus().focusable = true; │ │448 tb.getFocus().kind = .{ .custom = link }; │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯