Accepts a single line of content. line should not have a trailing line
ending character.
pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void
pub fn feedLine(p: *Parser, line: []const u8) Allocator.Error!void {
var rest_line = line;
const first_unmatched = for (p.pending_blocks.items, 0..) |b, i| {
if (b.match(rest_line)) |rest| {
rest_line = rest;
} else {
break i;
}
} else p.pending_blocks.items.len;
const in_code_block = p.pending_blocks.items.len > 0 and
p.pending_blocks.getLast().?.tag == .code_block;
const code_block_end = in_code_block and
first_unmatched + 1 == p.pending_blocks.items.len;
// New blocks cannot be started if we are actively inside a code block or
// are just closing one (to avoid interpreting the closing ``` as a new code
// block start).
var maybe_block_start = if (!in_code_block or first_unmatched + 2 <= p.pending_blocks.items.len)
try p.startBlock(rest_line)
else
null;
// This is a lazy continuation line if there are no new blocks to open and
// the last open block is a paragraph.
if (maybe_block_start == null and
!isBlank(rest_line) and
p.pending_blocks.items.len > 0 and
p.pending_blocks.getLast().?.tag == .paragraph)
{
try p.addScratchStringLine(mem.trimStart(u8, rest_line, " \t"));
return;
}
// If a new block needs to be started, any paragraph needs to be closed,
// even though this isn't detected as part of the closing condition for
// paragraphs.
if (maybe_block_start != null and
p.pending_blocks.items.len > 0 and
p.pending_blocks.getLast().?.tag == .paragraph)
{
try p.closeLastBlock();
}
while (p.pending_blocks.items.len > first_unmatched) {
try p.closeLastBlock();
}
while (maybe_block_start) |block_start| : (maybe_block_start = try p.startBlock(rest_line)) {
try p.appendBlockStart(block_start);
// There may be more blocks to start within the same line.
rest_line = block_start.rest;
// Headings may only contain inline content.
if (block_start.tag == .heading) break;
// An opening code fence does not contain any additional block or inline
// content to process.
if (block_start.tag == .code_block) return;
}
// Do not append the end of a code block (```) as textual content.
if (code_block_end) return;
const can_accept = if (p.pending_blocks.getLast()) |last_pending_block|
last_pending_block.canAccept()
else
.blocks;
const rest_line_trimmed = mem.trimStart(u8, rest_line, " \t");
switch (can_accept) {
.blocks => {
// If we're inside a list item and the rest of the line is blank, it
// means that any subsequent child of the list item (or subsequent
// item in the list) will cause the containing list to be considered
// loose. However, we can't immediately declare that the list is
// loose, since we might just be looking at a blank line after the
// end of the last item in the list. The final determination will be
// made when appending the next child of the list or list item.
const maybe_containing_list_index = if (p.pending_blocks.items.len > 0 and p.pending_blocks.getLast().?.tag == .list_item)
p.pending_blocks.items.len - 2
else
null;
if (rest_line_trimmed.len > 0) {
try p.appendBlockStart(.{
.tag = .paragraph,
.data = .{ .none = {} },
.rest = undefined,
});
try p.addScratchStringLine(rest_line_trimmed);
}
if (maybe_containing_list_index) |containing_list_index| {
p.pending_blocks.items[containing_list_index].data.list.last_line_blank = rest_line_trimmed.len == 0;
}
},
.inlines => try p.addScratchStringLine(rest_line_trimmed),
.raw_inlines => try p.addScratchStringLine(rest_line),
.nothing => {},
}
}