use crate::options::ParserOptions;
use crate::syntax::SyntaxKind;
use rowan::GreenNodeBuilder;
use super::blockquotes::strip_n_blockquote_markers;
use super::code_blocks::{emit_content_line_prefixes, strip_list_indent};
use super::container_prefix::advance_columns;
use crate::parser::utils::container_stack::byte_index_at_column;
use crate::parser::utils::helpers::strip_newline;
use crate::parser::utils::inline_emission;
pub fn try_parse_line_block_start(line: &str) -> Option<()> {
let trimmed = line.trim_start();
if trimmed.starts_with("| ") || trimmed == "|" {
Some(())
} else {
None
}
}
#[allow(clippy::too_many_arguments)]
pub fn parse_line_block(
lines: &[&str],
start_pos: usize,
builder: &mut GreenNodeBuilder<'static>,
config: &ParserOptions,
bq_depth: usize,
list_content_col: usize,
list_marker_consumed_on_line_0: bool,
bq_outer: bool,
content_indent: usize,
) -> usize {
log::trace!("Parsing line block at line {}", start_pos + 1);
builder.start_node(SyntaxKind::LINE_BLOCK.into());
let mut pos = start_pos;
let mut first_line = true;
while pos < lines.len() {
let raw_line = lines[pos];
let kind = if first_line {
LineKind::Marker
} else {
let peek = silent_strip_container_prefix(
raw_line,
bq_depth,
list_content_col,
bq_outer,
content_indent,
);
if parse_line_block_line_marker(peek).is_some() {
LineKind::Marker
} else if peek.starts_with(' ') && !peek.trim_start().starts_with("| ") {
LineKind::Continuation
} else {
break;
}
};
builder.start_node(SyntaxKind::LINE_BLOCK_LINE.into());
let stripped = if first_line {
emit_open_line_prefixes(
builder,
raw_line,
bq_depth,
list_content_col,
list_marker_consumed_on_line_0,
bq_outer,
content_indent,
)
} else {
emit_content_line_prefixes(
builder,
raw_line,
bq_depth,
list_content_col,
bq_outer,
content_indent,
)
};
match kind {
LineKind::Marker => {
let content_start = parse_line_block_line_marker(stripped)
.expect("marker presence verified upstream");
builder.token(
SyntaxKind::LINE_BLOCK_MARKER.into(),
&stripped[..content_start],
);
let content = &stripped[content_start..];
let (content_without_newline, newline_str) = strip_newline(content);
if !content_without_newline.is_empty() {
inline_emission::emit_inlines(builder, content_without_newline, config, false);
}
if !newline_str.is_empty() {
builder.token(SyntaxKind::NEWLINE.into(), newline_str);
}
}
LineKind::Continuation => {
let (line_without_newline, newline_str) = strip_newline(stripped);
if !line_without_newline.is_empty() {
inline_emission::emit_inlines(builder, line_without_newline, config, false);
}
if !newline_str.is_empty() {
builder.token(SyntaxKind::NEWLINE.into(), newline_str);
}
}
}
builder.finish_node(); pos += 1;
first_line = false;
}
builder.finish_node();
log::trace!("Parsed line block: lines {}-{}", start_pos + 1, pos);
pos
}
enum LineKind {
Marker,
Continuation,
}
fn silent_strip_container_prefix<'a>(
line: &'a str,
bq_depth: usize,
list_content_col: usize,
bq_outer: bool,
content_indent: usize,
) -> &'a str {
let mut s = line;
let strip_bq = |s: &mut &'a str| {
if bq_depth > 0 {
*s = strip_n_blockquote_markers(s, bq_depth);
}
};
let strip_list = |s: &mut &'a str| {
if list_content_col > 0 {
*s = strip_list_indent(s, list_content_col);
}
};
if bq_outer {
strip_bq(&mut s);
strip_list(&mut s);
} else {
strip_list(&mut s);
strip_bq(&mut s);
}
if content_indent > 0 {
let indent_bytes = byte_index_at_column(s, content_indent);
if s.len() >= indent_bytes {
s = &s[indent_bytes..];
}
}
s
}
fn emit_open_line_prefixes<'a>(
builder: &mut GreenNodeBuilder<'static>,
source_line: &'a str,
bq_depth: usize,
list_content_col: usize,
list_marker_consumed_on_line_0: bool,
bq_outer: bool,
content_indent: usize,
) -> &'a str {
let mut s: &'a str = source_line;
let mut pending_ws_start: Option<usize> = None;
let suppress_list = list_marker_consumed_on_line_0;
let flush_ws = |builder: &mut GreenNodeBuilder<'static>,
pending: &mut Option<usize>,
current_offset: usize| {
if let Some(start) = *pending
&& current_offset > start
{
builder.token(
SyntaxKind::WHITESPACE.into(),
&source_line[start..current_offset],
);
}
*pending = None;
};
let do_strip_list = |s: &mut &'a str, pending: &mut Option<usize>| {
if list_content_col == 0 {
return;
}
let stripped = if suppress_list {
advance_columns(s, list_content_col)
} else {
strip_list_indent(s, list_content_col)
};
let consumed = s.len() - stripped.len();
if consumed > 0 {
let start = source_line.len() - s.len();
if !suppress_list && pending.is_none() {
*pending = Some(start);
}
*s = stripped;
}
};
let do_strip_bq =
|builder: &mut GreenNodeBuilder<'static>, s: &mut &'a str, pending: &mut Option<usize>| {
if bq_depth == 0 {
return;
}
let current_offset = source_line.len() - s.len();
flush_ws(builder, pending, current_offset);
*s = strip_n_blockquote_markers(s, bq_depth);
};
if bq_outer {
do_strip_bq(builder, &mut s, &mut pending_ws_start);
do_strip_list(&mut s, &mut pending_ws_start);
} else {
do_strip_list(&mut s, &mut pending_ws_start);
do_strip_bq(builder, &mut s, &mut pending_ws_start);
}
if content_indent > 0 {
let indent_bytes = byte_index_at_column(s, content_indent);
if s.len() >= indent_bytes && indent_bytes > 0 {
let start = source_line.len() - s.len();
if pending_ws_start.is_none() {
pending_ws_start = Some(start);
}
s = &s[indent_bytes..];
}
}
let final_offset = source_line.len() - s.len();
flush_ws(builder, &mut pending_ws_start, final_offset);
s
}
fn parse_line_block_line_marker(line: &str) -> Option<usize> {
let trimmed_start = line.len() - line.trim_start().len();
let after_indent = &line[trimmed_start..];
if after_indent.starts_with("| ") {
Some(trimmed_start + 2) } else if after_indent == "|" || after_indent == "|\n" {
Some(trimmed_start + 1) } else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_try_parse_line_block_start() {
assert!(try_parse_line_block_start("| Some text").is_some());
assert!(try_parse_line_block_start("| ").is_some());
assert!(try_parse_line_block_start("|").is_some()); assert!(try_parse_line_block_start(" | Some text").is_some());
assert!(try_parse_line_block_start("|No space").is_none());
assert!(try_parse_line_block_start("Regular text").is_none());
assert!(try_parse_line_block_start("").is_none());
}
#[test]
fn test_parse_line_block_marker() {
assert_eq!(parse_line_block_line_marker("| Some text"), Some(2));
assert_eq!(parse_line_block_line_marker("| "), Some(2));
assert_eq!(parse_line_block_line_marker("|"), Some(1)); assert_eq!(parse_line_block_line_marker(" | Indented"), Some(4));
assert_eq!(parse_line_block_line_marker("|No space"), None);
assert_eq!(parse_line_block_line_marker("Regular"), None);
}
#[test]
fn test_simple_line_block() {
let input = vec!["| Line one", "| Line two", "| Line three"];
let mut builder = GreenNodeBuilder::new();
let new_pos = parse_line_block(
&input,
0,
&mut builder,
&ParserOptions::default(),
0,
0,
false,
false,
0,
);
assert_eq!(new_pos, 3);
}
#[test]
fn test_line_block_with_continuation() {
let input = vec![
"| This is a long line",
" that continues here",
"| Second line",
];
let mut builder = GreenNodeBuilder::new();
let new_pos = parse_line_block(
&input,
0,
&mut builder,
&ParserOptions::default(),
0,
0,
false,
false,
0,
);
assert_eq!(new_pos, 3);
}
#[test]
fn test_line_block_with_indentation() {
let input = vec!["| First line", "| Indented line", "| Back to normal"];
let mut builder = GreenNodeBuilder::new();
let new_pos = parse_line_block(
&input,
0,
&mut builder,
&ParserOptions::default(),
0,
0,
false,
false,
0,
);
assert_eq!(new_pos, 3);
}
#[test]
fn test_line_block_stops_at_non_line_block() {
let input = vec!["| Line one", "| Line two", "Regular paragraph"];
let mut builder = GreenNodeBuilder::new();
let new_pos = parse_line_block(
&input,
0,
&mut builder,
&ParserOptions::default(),
0,
0,
false,
false,
0,
);
assert_eq!(new_pos, 2); }
}