╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ │694 // reserve the nav row's height (the shrink scroll frame yields it). │ │695 self.detailOuter().children.values()[detail_nav_index].min_size = │ │696 if (nav_box.children.count() > 0) .{ .width = null, .height = row_height } else null;│ │697 + │ │698 + // rendered markdown wraps to the pane's width, so it only scrolls down │ │699 + const sc = self.detailScroll(); │ │700 + sc.options.direction = if (markdown) .vert else .both; │ │701 │ │702 // reset the scroll to the top for the newly-shown file: directly on the │ │703 // terminal (the wasm offset), and via a version bump on the web (so the │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │704 // renderer's scroll id changes and JS drops the preserved position).│ │688 - const sc = self.detailScroll(); │ │705 sc.x = 0; │ │706 sc.y = 0; │ │707 sc.getFocus().version +%= 1; │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮ │740 } │ │741 if (direction == .up and self.contentAtTop() and self.focusHeader(root_focus)) return;│ │742 if (self.detailActive()) { │ │727 - try self.detailInput(key, root_focus); │ │743 + try self.detailInput(allocator, key, root_focus); │ │744 } else { │ │745 try self.listInput(key, root_focus); │ │746 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │772 // whether focus is on the content box (vs. a "previous"/"next" nav link). │ │773 fn focusOnContent(self: *View, root_focus: *Focus) bool { │ │774 const g = root_focus.grandchild_id orelse return false; │ │775 + if (self.markdownView()) |view| return view.owns(g); │ │776 return self.detailInner().children.contains(g); │ │777 + } │ │778 + │ │779 + // the rendered markdown in the detail pane, if that's what it shows │ │780 + fn markdownView(self: *View) ?*Markdown.View { │ │781 + const inner = self.detailInner(); │ │782 + if (inner.children.count() == 0) return null; │ │783 + return switch (inner.children.values()[0].widget) { │ │784 + .markdown => |*view| view, │ │785 + else => null, │ │786 + }; │ │787 } │ │788 │ │762 - fn detailInput(self: *View, key: Key, root_focus: *Focus) !void { │ │789 + fn detailInput(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void {│ │790 const sc = self.detailScroll(); │ │791 const on_content = self.focusOnContent(root_focus); │ │792 // the "next" link sits above the content scroll. on the link, left returns │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │849 self.detailScroll().clampToContent(); │ │850 }, │ │851 else => {}, │ │852 + }, │ │853 + // step between the rendered markdown's links; the web leaves tab to the browser │ │854 + .tab, .back_tab => if (on_content and self.session.is_terminal) if (self.markdownView()) |view| {│ │855 + try view.stepLink(allocator, root_focus, sc, key == .tab); │ │856 }, │ │857 else => {}, │ │858 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │867 } │ │868 │ │869 fn focusContent(self: *View, root_focus: *Focus) void { │ │870 + if (self.markdownView()) |view| return root_focus.setFocus(view.body.id); │ │871 const inner = self.detailInner(); │ │872 const id = inner.getFocus().child_id orelse (if (inner.children.count() > 0) inner.children.keys()[0] else return);│ │873 root_focus.setFocus(id); │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │972 return null; │ │973 } │ │974 │ │975 +fn isMarkdown(name: []const u8) bool { │ │976 + const extension = std.fs.path.extension(name); │ │977 + return std.ascii.eqlIgnoreCase(extension, ".md") or std.ascii.eqlIgnoreCase(extension, ".markdown");│ │978 +} │ │979 + │ │980 fn isReadme(name: []const u8) bool { │ │981 return std.ascii.eqlIgnoreCase(name, "README") or std.ascii.eqlIgnoreCase(name, "README.md"); │ │982 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ added ─────────────────╮ │src/ui/Repo/Markdown.zig│ ╰────────────────────────╯ ╭ changed ──────────╮ │src/ui/SubTitle.zig│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │18 }; │ │19 } │ │20 │ │21 +pub fn width(self: Self) !usize { │ │22 + const line_end = std.mem.indexOfScalar(u8, self.content, '\n') orelse self.content.len;│ │23 + return xitui.width.displayWidth(self.content[0..line_end]); │ │24 +} │ │25 + │ │26 pub const View = struct { │ │27 text_box: wgt.TextBox, │ │28 │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │391 }; │ │392 } │ │393 │ │394 +// whether every char of `text` has a glyph │ │395 +pub fn covers(text: []const u8) bool { │ │396 + for (text) |c| { │ │397 + if (glyphFor(c) == null) return false; │ │398 + } │ │399 + return true; │ │400 +} │ │401 + │ │402 // map a 2x3 sub-pixel pattern to its corresponding sextant codepoint│ │403 fn sextantCodepoint(pattern: u6) u21 { │ │404 return switch (pattern) { │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯