╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ──────────────╮ │src/ui/Repo/Commits.zig│ ╰───────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │213 .base_oid = try aa.dupe(u8, base_oid), │ │214 .commit_count = if (repo_kind == .xit and location == .repo) blk: { │ │215 if (base_oid.len == 0) break :blk repo.commitCount(io, gpa, .{ .oid = &resolved.oid }) catch null; │ │163 - var moment = repo.core.latestMoment() catch break :blk null; │ │164 - const state = rp.Repo(.xit, repo_opts).State(.read_only){ .core = &repo.core, .extra = .{ .moment = &moment } };│ │216 break :blk evt.PatchRev.commitCount(repo_kind, repo_opts, state, io, gpa, base_oid, &resolved.oid) catch null; │ │217 } else null, │ │218 .commits = try aa.dupe(Commit, buf[0..count]), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │219 .next_start = next_start, │ │220 .content = try pageContent(aa, content), │ │221 + .search = query, │ │222 + .search_available = search_available, │ │223 + }; │ │224 +} │ │225 + │ │226 +// one commit's row: its message, author and stats. the diff window is rendered │ │227 +// afterward, once the log iterator is closed. │ │228 +fn commitEntry( │ │229 + comptime repo_kind: rp.RepoKind, │ │230 + comptime repo_opts: rp.RepoOpts(repo_kind), │ │231 + arena: *std.heap.ArenaAllocator, │ │232 + repo: *rp.Repo(repo_kind, repo_opts), │ │233 + io: std.Io, │ │234 + gpa: std.mem.Allocator, │ │235 + admin_moment: ?evt.AdminDB.HashMap(.read_only), │ │236 + commit_object: *obj.Object(repo_kind, repo_opts), │ │237 + full_message: bool, │ │238 +) !Commit { │ │239 + const aa = arena.allocator(); │ │240 + const md = commit_object.content.commit.metadata; │ │241 + const text, const truncated = try readMessage(repo_kind, repo_opts, aa, commit_object, full_message); │ │242 + return .{ │ │243 + .oid = try aa.dupe(u8, &commit_object.oid), │ │244 + .date = try formatDate(aa, md.timestamp), │ │245 + .message = text, │ │246 + .message_truncated = truncated, │ │247 + .author = try ui.Author.init(admin_moment, arena, md.author orelse ""), │ │248 + .stats = if (repo_kind == .xit) try repo.commitStats(io, gpa, .{ .oid = &commit_object.oid }) else null,│ │249 }; │ │250 } │ │251 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │392 var outer = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │393 errdefer outer.deinit(allocator); │ │394 │ │316 - // the clone url at the top. │ │395 + // the search box and the clone url at the top. │ │396 { │ │318 - var header_view = try Header.View.init(allocator, session, data.location); │ │397 + var header_view = try ui.widget.SearchHeader.init(allocator, session, data.location, " search ", "search", data.search, data.search_available); │ │398 errdefer header_view.deinit(allocator); │ │320 - try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .repo_commits_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 3 } });│ │399 + try outer.children.put(allocator, header_view.getFocus().id, .{ .widget = .{ .search_header = header_view }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │400 } │ │401 │ │402 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .horiz }); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │415 try addRow(allocator, &list_box, firstLine(commit.message), try commitRowLink(session.page_arena, data, commit, content)); │ │416 } │ │417 if (data.next_start) |next| { │ │339 - try addRow(allocator, &list_box, "next →", try commitsLink(session.page_arena, data, next, 0, "")); │ │418 + try addRow(allocator, &list_box, "next →", try nextPageLink(session.page_arena, data, next)); │ │419 } │ │420 if (list_box.children.count() > 0) list_box.getFocus().child_id = list_box.children.keys()[0]; │ │421 break :blk try wgt.Scroll(ui.Widget).init(allocator, .{ .box = list_box }, .{ .direction = .vert, .web_native = !session.is_terminal, .fill = true });│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │447 box.getFocus().child_id = box.children.keys()[list_index]; │ │448 try outer.children.put(allocator, box.getFocus().id, .{ .widget = .{ .box = box }, .rect = null, .min_size = null });│ │449 │ │371 - // focus lives in the split; the banner isn't focusable. │ │372 - outer.getFocus().child_id = outer.children.keys()[content_index]; │ │450 + // focus lives in the split, except that search results start in the │ │451 + // search box so the query can be refined right away. │ │452 + outer.getFocus().child_id = outer.children.keys()[if (data.search != null) header_index else content_index]; │ │453 │ │454 return .{ │ │455 .box = outer, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │514 return &self.box.children.values()[content_index].widget.box; │ │515 } │ │516 │ │437 - fn header(self: *View) *Header.View { │ │438 - return &self.box.children.values()[header_index].widget.repo_commits_header;│ │517 + fn header(self: *View) *ui.widget.SearchHeader { │ │518 + return &self.box.children.values()[header_index].widget.search_header; │ │519 } │ │520 │ │521 fn headerActive(self: *View) bool { │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │688 if (self.headerActive()) { │ │689 if (key == .arrow_down) { │ │690 root_focus.setFocus(self.contentBox().getFocus().id); │ │611 - } else { │ │612 - try self.header().input(allocator, key, root_focus); │ │691 + return; │ │692 } │ │614 - return; │ │693 + if (key == .enter) if (try self.header().submittedText(allocator)) |text| { │ │694 + defer allocator.free(text); │ │695 + return self.submit(text); │ │696 + }; │ │697 + return self.header().input(allocator, key, root_focus); │ │698 } │ │616 - if (key == .arrow_up and self.contentAtTop() and self.header().focusCloneUrl(root_focus)) return;│ │699 + if (key == .arrow_up and self.contentAtTop() and self.header().focusHeader(root_focus)) return; │ │700 if (self.diffActive()) { │ │701 if (key == .arrow_left and self.diffScroll().x == 0) { │ │702 self.focusList(root_focus); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │706 } else { │ │707 try self.listInput(key, root_focus); │ │708 } │ │709 + } │ │710 + │ │711 + // navigate to the typed query's results, pinned to the list's ref. an │ │712 + // empty query leaves the results for the plain log. │ │713 + fn submit(self: *View, text: []const u8) !void { │ │714 + if (text.len == 0 and self.data.search == null) return; │ │715 + const route = self.data.location.commitsRoute(self.data.ref_or_oid, self.data.ref_or_oid_value, 0, "", self.data.base_oid) orelse return;│ │716 + try self.session.navigate(route.withSearch(text) orelse return); │ │717 } │ │718 │ │719 fn listInput(self: *View, key: Key, root_focus: *Focus) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │728 .enter => if (self.selectedCommitIndex() != null) │ │729 self.focusDiff(root_focus) │ │730 else if (self.data.next_start) |next| { │ │640 - if (self.data.location.commitsRoute(.object, next, 0, "", self.data.base_oid)) |route|│ │641 - try self.session.navigate(route); │ │731 + if (pageRoute(self.data, next)) |route| try self.session.navigate(route); │ │732 }, │ │733 .arrow_right => self.focusDiff(root_focus), │ │734 else => {}, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │773 return lb.children.getIndex(cid) == 0; │ │774 } │ │775 │ │686 - // only the clone-url row sits directly below the repository header. │ │776 + // only the header's own row sits directly below the repository header. │ │777 pub fn atTop(self: *View) bool { │ │688 - return self.headerActive() or (!self.header().hasCloneUrl() and self.contentAtTop()); │ │778 + return self.headerActive() or (!self.header().hasFocusable() and self.contentAtTop());│ │779 } │ │780 │ │691 - pub fn focusCloneUrl(self: *View, root_focus: *Focus) bool { │ │692 - return self.header().focusCloneUrl(root_focus); │ │781 + pub fn focusHeader(self: *View, root_focus: *Focus) bool { │ │782 + return self.header().focusHeader(root_focus); │ │783 } │ │784 }; │ │785 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯