╭ changed ──╮ │src/try.zig│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │278 try readme.writeStreamingAll(io, │ │279 \\# Sample Repo │ │280 \\ │ │281 - \\A repository seeded with test data for exercising the UI. │ │281 + \\A repository seeded with test data for exercising the UI. Text can be│ │282 + \\*italic*, **bold**, ***both*** or ~~struck through~~, and links go to│ │283 + \\[the contributing guide](docs/dev/contribute.md) or to │ │284 + \\[an external site](https://github.com/xit-vcs/haxy). │ │285 \\ │ │286 \\## Features │ │287 \\ │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │288 \\- Fast and lightweight │ │289 \\- Zero configuration required │ │290 + \\ - Sensible defaults │ │291 + \\ - Nothing to edit │ │292 \\- Works out of the box │ │293 \\ │ │294 + \\## Roadmap │ │295 + \\ │ │296 + \\- [x] Render markdown │ │297 + \\- [ ] Render tables │ │298 + \\ │ │299 + \\> Simplicity is prerequisite for reliability. A quote this long wraps│ │300 + \\> onto more than one line in a narrow pane. │ │301 + \\ │ │302 + \\--- │ │303 + \\ │ │304 \\## Installation │ │305 \\ │ │306 \\Clone the repository and build it from source: │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │332 \\any significant changes before submitting a pull request. │ │333 \\See the docs under `docs/dev` for more details. │ │334 \\ │ │335 + \\## A Heading Far Too Long To Fit In The Subtitle Font Within A Pane│ │336 + \\ │ │337 + \\Long headings fall back to bold text. │ │338 + \\ │ │339 \\## License │ │340 \\ │ │341 \\Released under the MIT License. See `LICENSE` for the full │ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2538 } │ │2539 │ │2540 pub fn terminalWebUrl(allocator: std.mem.Allocator, session: *const Session, path: []const u8) ![]const u8 {│ │2541 + // an absolute url is already complete │ │2542 + if (!std.mem.startsWith(u8, path, "/")) return path; │ │2543 return if (session.web_port) |port| │ │2544 try std.fmt.allocPrint(allocator, "http://localhost:{d}{s}", .{ port, path }) │ │2545 else │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Repo.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────────╮ │14 │ │15 pub const Header = @import("./Repo/Header.zig"); │ │16 pub const Files = @import("./Repo/Files.zig"); │ │17 +pub const Markdown = @import("./Repo/Markdown.zig");│ │18 pub const Commits = @import("./Repo/Commits.zig"); │ │19 pub const Diff = @import("./Repo/Diff.zig"); │ │20 pub const Refs = @import("./Repo/Refs.zig"); │ ╰────────────────────────────────────────────────────────╯ ╭ changed ────────────╮ │src/ui/Repo/Files.zig│ ╰─────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │13 const Focus = xitui.focus.Focus; │ │14 const inp = @import("../input.zig"); │ │15 const fnd = @import("../../find.zig"); │ │16 +const md = @import("../../markdown.zig"); │ │17 +const Markdown = @import("Markdown.zig"); │ │18 │ │19 // how many lines of a file's content one window shows before a "next" link.│ │20 const file_page = 5000; │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │21 + │ │22 +// a longer markdown file shows as numbered source rather than rendered. │ │23 +const max_markdown_lines = 2000; │ │24 │ │25 // budget for the total bytes of file content preloaded (windowed) per page,│ │26 // so selecting a preloaded file shows its contents without a reload. │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │599 // the content scroll's border is single normally and double when the │ │600 // content is focused (the content box itself is borderless). │ │601 const has_content = self.detailInner().children.count() > 0; │ │597 - const content_focused = if (root_focus.grandchild_id) |g| self.detailInner().children.contains(g) else false; │ │598 - self.detailScrollFrame().options.border_style = if (!has_content) null else if (content_focused) .double else .single; │ │602 + self.detailScrollFrame().options.border_style = if (!has_content) null else if (self.focusOnContent(root_focus)) .double else .single;│ │603 │ │604 // cap the list at list_max_width only while the detail pane fits beside │ │605 // it. the box drops the detail pane when the width can't hold both │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │658 │ │659 // only files have contents to show; directories and the ".." row leave│ │660 // the pane empty. │ │661 + var markdown = false; │ │662 if (self.selectedEntry()) |entry| { │ │663 if (!entry.is_dir) { │ │664 // the window links sit above the scroll, so they stay put │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │673 try self.addContentBox(allocator, inner, "(press enter to load this file)", link); │ │674 } else if (entry.is_binary) { │ │675 try self.addContentBox(allocator, inner, "(binary file)", ""); │ │676 + } else if (isMarkdown(entry.name) and entry.window_start == 0 and !entry.has_more and entry.lines.len <= max_markdown_lines) { │ │677 + const page_arena = self.session.page_arena; │ │678 + const doc = try md.parse(page_arena.allocator(), entry.lines); │ │679 + const path = try childDir(page_arena.allocator(), self.data.dir, entry.name); │ │680 + var view = try Markdown.View.init(allocator, doc, self.data, path, page_arena); │ │681 + errdefer view.deinit(allocator); │ │682 + try inner.children.put(allocator, view.getFocus().id, .{ .widget = .{ .markdown = view }, .rect = null, .min_size = null });│ │683 + markdown = true; │ │684 } else { │ │685 const text = try numberedContent(self.session.page_arena.allocator(), entry.lines, entry.window_start); │ │686 try self.addContentBox(allocator, inner, text, ""); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯