summaryrefslogtreecommitdiff
path: root/src/markdown.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/markdown.zig')
-rw-r--r--src/markdown.zig83
1 files changed, 76 insertions, 7 deletions
diff --git a/src/markdown.zig b/src/markdown.zig
index a029756..fad1676 100644
--- a/src/markdown.zig
+++ b/src/markdown.zig
@@ -136,6 +136,11 @@ pub const Renderer = struct {
buf: std.ArrayList(u8) = .empty,
in_code_block: bool = false,
list_depth: usize = 0,
+ list_item_depth: usize = 0,
+ list_item_first_paragraph: bool = false,
+ list_item_depth_stack: [64]usize = [_]usize{0} ** 64,
+ list_item_first_stack: [64]bool = [_]bool{false} ** 64,
+ list_item_stack_len: usize = 0,
heading_level: usize = 0,
err: ?anyerror = null,
@@ -180,11 +185,44 @@ pub const Renderer = struct {
fn flushParagraph(self: *Renderer) !void {
const text = std.mem.trim(u8, self.buf.items, " \t\n");
if (text.len == 0) { self.buf.clearRetainingCapacity(); return; }
- var wrapped: std.ArrayList(u8) = .empty;
- defer wrapped.deinit(self.alloc);
- try wrapStyled(text, self.width, &wrapped, self.alloc);
- var it = std.mem.splitScalar(u8, wrapped.items, '\n');
- while (it.next()) |line| try self.appendLine(line);
+
+ if (self.list_item_depth > 0) {
+ var first_prefix: std.ArrayList(u8) = .empty;
+ defer first_prefix.deinit(self.alloc);
+ var cont_prefix: std.ArrayList(u8) = .empty;
+ defer cont_prefix.deinit(self.alloc);
+
+ const indent = (self.list_item_depth - 1) * 2;
+ try first_prefix.appendNTimes(self.alloc, ' ', indent);
+ try cont_prefix.appendNTimes(self.alloc, ' ', indent + 2);
+ if (self.list_item_first_paragraph) {
+ try first_prefix.appendSlice(self.alloc, "• ");
+ } else {
+ try first_prefix.appendNTimes(self.alloc, ' ', 2);
+ }
+
+ var wrapped: std.ArrayList(u8) = .empty;
+ defer wrapped.deinit(self.alloc);
+ const wrap_width = if (self.width > visibleWidth(first_prefix.items)) self.width - visibleWidth(first_prefix.items) else 1;
+ try wrapStyled(text, wrap_width, &wrapped, self.alloc);
+ var it = std.mem.splitScalar(u8, wrapped.items, '\n');
+ var first = true;
+ while (it.next()) |line| {
+ var tmp: std.ArrayList(u8) = .empty;
+ defer tmp.deinit(self.alloc);
+ try tmp.appendSlice(self.alloc, if (first) first_prefix.items else cont_prefix.items);
+ try tmp.appendSlice(self.alloc, line);
+ try self.appendLine(tmp.items);
+ first = false;
+ }
+ self.list_item_first_paragraph = false;
+ } else {
+ var wrapped: std.ArrayList(u8) = .empty;
+ defer wrapped.deinit(self.alloc);
+ try wrapStyled(text, self.width, &wrapped, self.alloc);
+ var it = std.mem.splitScalar(u8, wrapped.items, '\n');
+ while (it.next()) |line| try self.appendLine(line);
+ }
self.buf.clearRetainingCapacity();
}
@@ -222,7 +260,13 @@ pub const Renderer = struct {
c.MD_BLOCK_UL, c.MD_BLOCK_OL => self.list_depth += 1,
c.MD_BLOCK_LI => {
self.flushParagraph() catch |e| return self.fail(e);
- self.add("• ") catch |e| return self.fail(e);
+ if (self.list_item_stack_len < self.list_item_depth_stack.len) {
+ self.list_item_depth_stack[self.list_item_stack_len] = self.list_item_depth;
+ self.list_item_first_stack[self.list_item_stack_len] = self.list_item_first_paragraph;
+ self.list_item_stack_len += 1;
+ }
+ self.list_item_depth = self.list_depth;
+ self.list_item_first_paragraph = true;
},
c.MD_BLOCK_HR => self.appendLine("────────") catch |e| return self.fail(e),
else => {},
@@ -240,9 +284,20 @@ pub const Renderer = struct {
self.appendBlank() catch |e| return self.fail(e);
self.heading_level = 0;
},
- c.MD_BLOCK_P, c.MD_BLOCK_LI => {
+ c.MD_BLOCK_P => {
self.flushParagraph() catch |e| return self.fail(e);
},
+ c.MD_BLOCK_LI => {
+ self.flushParagraph() catch |e| return self.fail(e);
+ if (self.list_item_stack_len > 0) {
+ self.list_item_stack_len -= 1;
+ self.list_item_depth = self.list_item_depth_stack[self.list_item_stack_len];
+ self.list_item_first_paragraph = self.list_item_first_stack[self.list_item_stack_len];
+ } else {
+ self.list_item_depth = 0;
+ self.list_item_first_paragraph = false;
+ }
+ },
c.MD_BLOCK_CODE => {
self.flushCodeBlock() catch |e| return self.fail(e);
self.appendBlank() catch |e| return self.fail(e);
@@ -330,3 +385,17 @@ test "Renderer uses MD4C for common markdown" {
}
try testing.expect(found_code);
}
+
+test "Renderer preserves nested list indentation" {
+ var out: std.ArrayList([]const u8) = .empty;
+ defer {
+ for (out.items) |l| testing.allocator.free(l);
+ out.deinit(testing.allocator);
+ }
+ var r: Renderer = .{ .alloc = testing.allocator, .width = 80, .out_lines = &out };
+ try r.render("- top\n - child\n- next\n");
+ try testing.expectEqual(@as(usize, 3), out.items.len);
+ try testing.expectEqualStrings("• top", out.items[0]);
+ try testing.expectEqualStrings(" • child", out.items[1]);
+ try testing.expectEqualStrings("• next", out.items[2]);
+}