//! Fourth part of the first pass: HTML blocks, code blocks, headings, lists.
///|
/// When start_ix is at the beginning of an HTML block of type 1 to 5,
/// this will find the end of the block.
fn FirstPass::parse_html_block_type_1_to_5(
self : FirstPass,
start_ix : Int,
html_end_tag : String,
remaining_space : Int,
indent : Int,
) -> Int {
let mut remaining_space = remaining_space
let mut indent = indent
ignore(self.tree.append(Item::{ start: start_ix, end: 0, body: HtmlBlock }))
ignore(self.tree.push())
let bytes = self.text
let mut ix = start_ix
let mut end_ix = 0
while true {
let line_start_ix = ix
ix += scan_nextline(bytes.view(start=ix))
self.append_html_line(remaining_space.max(indent), line_start_ix, ix)
let line_start = LineStart::new(bytes.view(start=ix))
let n_containers = line_start.scan_containers(self.tree, self.options)
guard n_containers >= self.tree.spine_len() else {
end_ix = ix
break
}
guard !bytes_contains_str(self.text, line_start_ix, ix, html_end_tag) else {
end_ix = ix
break
}
let next_line_ix = ix + line_start.bytes_scanned()
guard next_line_ix != self.text.length() else {
end_ix = next_line_ix
break
}
ix = next_line_ix
remaining_space = line_start.remaining_space()
indent = 0
}
self.pop(end_ix)
ix
}
///|
/// When start_ix is at the beginning of an HTML block of type 6 or 7.
fn FirstPass::parse_html_block_type_6_or_7(
self : FirstPass,
start_ix : Int,
remaining_space : Int,
indent : Int,
) -> Int {
let mut remaining_space = remaining_space
let mut indent = indent
ignore(self.tree.append(Item::{ start: start_ix, end: 0, body: HtmlBlock }))
ignore(self.tree.push())
let bytes = self.text
let mut ix = start_ix
let mut end_ix = 0
while true {
let line_start_ix = ix
ix += scan_nextline(bytes.view(start=ix))
self.append_html_line(remaining_space.max(indent), line_start_ix, ix)
let line_start = LineStart::new(bytes.view(start=ix))
let n_containers = line_start.scan_containers(self.tree, self.options)
let at_eol = line_start.is_at_eol()
guard n_containers >= self.tree.spine_len() && !at_eol else {
end_ix = ix
break
}
let next_line_ix = ix + line_start.bytes_scanned()
let is_end = next_line_ix == self.text.length() ||
scan_blank_line(bytes.view(start=next_line_ix)) is Some(_)
guard !is_end else {
end_ix = next_line_ix
break
}
ix = next_line_ix
remaining_space = line_start.remaining_space()
indent = 0
}
self.pop(end_ix)
ix
}
///|
fn FirstPass::parse_indented_code_block(
self : FirstPass,
start_ix : Int,
remaining_space : Int,
) -> Int {
let mut remaining_space = remaining_space
ignore(
self.tree.append(Item::{ start: start_ix, end: 0, body: IndentCodeBlock }),
)
ignore(self.tree.push())
let bytes = self.text
let mut last_nonblank_child : Int? = None
let mut last_nonblank_ix = 0
let mut end_ix = 0
self.last_line_blank = false
let mut ix = start_ix
while true {
let line_start_ix = ix
ix += scan_nextline(bytes.view(start=ix))
self.append_code_text(remaining_space, line_start_ix, ix)
if !self.last_line_blank {
last_nonblank_child = self.tree.cur()
last_nonblank_ix = ix
end_ix = ix
}
let line_start = LineStart::new(bytes.view(start=ix))
let n_containers = line_start.scan_containers(self.tree, self.options)
let cont = n_containers >= self.tree.spine_len() &&
(line_start.scan_space(4) || line_start.is_at_eol())
guard cont else { break }
let next_line_ix = ix + line_start.bytes_scanned()
guard next_line_ix != self.text.length() else { break }
ix = next_line_ix
remaining_space = line_start.remaining_space()
self.last_line_blank = scan_blank_line(bytes.view(start=ix)) is Some(_)
}
// Trim trailing blank lines.
match last_nonblank_child {
Some(child) => {
self.tree.nodes[child].next = None
self.tree.nodes[child].item.end = last_nonblank_ix
}
None => ()
}
self.pop(end_ix)
ix
}
///|
fn FirstPass::parse_fenced_code_block(
self : FirstPass,
start_ix : Int,
indent : Int,
fence_ch : Byte,
n_fence_char : Int,
) -> Int {
let bytes = self.text
let mut info_start = start_ix + n_fence_char
info_start += scan_whitespace_no_nl(bytes.view(start=info_start))
let mut ix = info_start + scan_nextline(bytes.view(start=info_start))
let info_end = ix -
scan_rev_while(bytes.view(start=info_start, end=ix), fn(c) {
c.to_char().is_ascii_whitespace()
})
let info_string = unescape(
self.text,
info_start,
info_end,
self.tree.is_in_table(),
)
ignore(
self.tree.append(Item::{
start: start_ix,
end: 0, // will get set later
body: FencedCodeBlock(self.allocs.allocate_cow(info_string)),
}),
)
ignore(self.tree.push())
while true {
let line_start = LineStart::new(bytes.view(start=ix))
let n_containers = line_start.scan_containers(self.tree, self.options)
guard n_containers >= self.tree.spine_len() else {
// this line will get parsed again as not being part of the code
self.pop(ix)
return ix
}
ignore(line_start.scan_space(indent))
let close_line_start = line_start.clone()
if !close_line_start.scan_space(4 - indent) {
let close_ix = ix + close_line_start.bytes_scanned()
match
scan_closing_code_fence(
bytes.view(start=close_ix),
fence_ch,
n_fence_char,
) {
Some(n) => {
ix = close_ix + n
self.pop(ix)
// try to read trailing whitespace or it will register as a completely blank line
return scan_blank_line(bytes.view(start=ix)).map_or(ix, fn(blank) {
ix + blank
})
}
None => ()
}
}
let remaining_space = line_start.remaining_space()
ix += line_start.bytes_scanned()
let next_ix = ix + scan_nextline(bytes.view(start=ix))
self.append_code_text(remaining_space, ix, next_ix)
ix = next_ix
}
ix
}
///|
fn FirstPass::parse_metadata_block(
self : FirstPass,
start_ix : Int,
metadata_block_ch : Byte,
) -> Int {
let bytes = self.text
let metadata_block_kind = if metadata_block_ch == b'-' {
YamlStyle
} else {
PlusesStyle
}
// 3 delimiter characters
let mut ix = start_ix + 3 + scan_nextline(bytes.view(start=start_ix + 3))
ignore(
self.tree.append(Item::{
start: start_ix,
end: 0, // will get set later
body: MetadataBlock(metadata_block_kind),
}),
)
ignore(self.tree.push())
while true {
let line_start = LineStart::new(bytes.view(start=ix))
let n_containers = line_start.scan_containers(self.tree, self.options)
guard n_containers >= self.tree.spine_len() else { break }
let (indent, _) = calc_indent(bytes.view(start=ix), 4)
if indent == 0 {
match
scan_closing_metadata_block(bytes.view(start=ix), metadata_block_ch) {
Some(n) => {
ix += n
break
}
None => ()
}
}
let remaining_space = line_start.remaining_space()
ix += line_start.bytes_scanned()
let next_ix = ix + scan_nextline(bytes.view(start=ix))
self.append_code_text(remaining_space, ix, next_ix)
ix = next_ix
}
self.pop(ix)
// try to read trailing whitespace or it will register as a completely blank line
scan_blank_line(bytes.view(start=ix)).map_or(ix, fn(n) { ix + n })
}
///|
fn FirstPass::append_code_text(
self : FirstPass,
remaining_space : Int,
start : Int,
end : Int,
) -> Unit {
let mut start = start
if remaining_space > 0 {
let spaces = String::repeat(" ", remaining_space)
let cow_ix = self.allocs.allocate_cow(spaces)
ignore(
self.tree.append(Item::{ start, end: start, body: SynthesizeText(cow_ix) }),
)
}
while true {
match bytes_find_byte(self.text, start, end, 0) {
Some(offset) => {
self.tree.append_text(start, start + offset, false)
ignore(
self.tree.append(Item::{
start: start + offset,
end: start + offset + 1,
body: SynthesizeChar('\u{fffd}'),
}),
)
start += offset + 1
}
None => break
}
}
if self.text.unsafe_get(end - 2) == b'\r' {
// Normalize CRLF to LF
self.tree.append_text(start, end - 2, false)
self.tree.append_text(end - 1, end, false)
} else {
self.tree.append_text(start, end, false)
}
}
///|
/// Appends a line of HTML to the tree.
fn FirstPass::append_html_line(
self : FirstPass,
remaining_space : Int,
start : Int,
end : Int,
) -> Unit {
if remaining_space > 0 {
let spaces = String::repeat(" ", remaining_space)
let cow_ix = self.allocs.allocate_cow(spaces)
ignore(
self.tree.append(Item::{ start, end: start, body: SynthesizeText(cow_ix) }),
)
}
if self.text.unsafe_get(end - 2) == b'\r' {
// Normalize CRLF to LF
ignore(self.tree.append(Item::{ start, end: end - 2, body: Html }))
ignore(self.tree.append(Item::{ start: end - 1, end, body: Html }))
} else {
ignore(self.tree.append(Item::{ start, end, body: Html }))
}
}
///|
/// Pop a container, setting its end.
fn FirstPass::pop(self : FirstPass, ix : Int) -> Unit {
let cur_ix = self.tree.pop().unwrap()
self.tree.nodes[cur_ix].item.end = ix
match self.tree.nodes[cur_ix].item.body {
DefinitionList(_) => {
fixup_end_of_definition_list(self.tree, cur_ix)
self.begin_list_item = None
}
_ => ()
}
match self.tree.nodes[cur_ix].item.body {
List(true, _, _) | DefinitionList(true) => {
surgerize_tight_list(self.tree, cur_ix)
self.begin_list_item = None
}
_ => ()
}
}
///|
/// Close a list if it's open. Also set loose if last line was blank
/// and end current list if it's a lone, empty item
fn FirstPass::finish_list(self : FirstPass, ix : Int) -> Unit {
self.finish_empty_list_item()
match self.tree.peek_up() {
Some(node_ix) =>
match self.tree.nodes[node_ix].item.body {
List(_, _, _) | DefinitionList(_) => self.pop(ix)
_ => ()
}
None => ()
}
if self.last_line_blank {
match self.tree.peek_grandparent() {
Some(node_ix) =>
match self.tree.nodes[node_ix].item.body {
List(_, ch, index) =>
self.tree.nodes[node_ix].item.body = List(false, ch, index)
DefinitionList(_) =>
self.tree.nodes[node_ix].item.body = DefinitionList(false)
_ => ()
}
None => ()
}
self.last_line_blank = false
}
}
///|
fn FirstPass::finish_empty_list_item(self : FirstPass) -> Unit {
match self.begin_list_item {
Some(begin_list_item) =>
if self.last_line_blank {
// A list item can begin with at most one blank line.
match self.tree.peek_up() {
Some(node_ix) =>
match self.tree.nodes[node_ix].item.body {
ListItem(_) | DefinitionListDefinition(_) =>
self.pop(begin_list_item)
_ => ()
}
None => ()
}
}
None => ()
}
self.begin_list_item = None
}
///|
/// Continue an existing list or start a new one if there's not an open
/// list that matches.
fn FirstPass::continue_list(
self : FirstPass,
start : Int,
ch : Byte,
index : Int64,
) -> Unit {
self.finish_empty_list_item()
match self.tree.peek_up() {
Some(node_ix) => {
match self.tree.nodes[node_ix].item.body {
List(_, existing_ch, index) =>
if existing_ch == ch {
if self.last_line_blank {
self.tree.nodes[node_ix].item.body = List(
false, existing_ch, index,
)
self.last_line_blank = false
}
return
}
_ => ()
}
self.finish_list(start)
}
None => ()
}
ignore(self.tree.append(Item::{ start, end: 0, body: List(true, ch, index) }))
ignore(self.tree.push())
self.last_line_blank = false
}
///|
/// Parse a thematic break.
fn FirstPass::parse_hrule(self : FirstPass, hrule_size : Int, ix : Int) -> Int {
ignore(
self.tree.append(Item::{ start: ix, end: ix + hrule_size, body: Rule }),
)
ix + hrule_size
}