use crate::markdown::escape::undo_backslash_escape;
use crate::markdown::line::{Line, LineType};
use crate::markdown::syntax_highlighter::SyntaxHighlighter;
use crate::utils::{drop_while, get_parenthesis_end_index};
use crate::consts::*;
use crate::utils::into_v16;
use std::str::FromStr;
#[derive(Default)]
struct CodeFenceOption {
language: Option<Vec<u16>>,
line_num: Option<i32>,
}
fn parse_code_fence_option(content: &Vec<u16>) -> CodeFenceOption {
let mut content = drop_while(content, U16_BACKTICK);
content = content.iter().filter(|c| *c != &U16_SPACE).map(|c| *c).collect();
let params = content.split(|c| *c == U16_COMMA);
let mut language = None;
let mut line_num = None;
'outer: for p in params {
'inner: for (ind, c) in p.iter().enumerate() {
if *c == U16_LEFT_PARENTHESIS {
match get_parenthesis_end_index(&p.to_vec(), ind) {
None => {break 'inner;}
Some(i) => {
match i32::from_str(&String::from_utf16_lossy(&p[ind + 1..i])) {
Err(_) => {break 'inner;}
Ok(n) => {line_num = Some(n); continue 'outer;}
}
}
}
}
}
language = Some(p.to_vec());
}
CodeFenceOption {
language, line_num
}
}
pub fn merge_code_fence(lines: &Vec<Line>, syntax_highlighter: &SyntaxHighlighter) -> Vec<Line> {
let mut curr_fenced_code = vec![];
let mut is_inside_fence = false;
let mut result = Vec::with_capacity(lines.len());
let mut code_fence_option = CodeFenceOption::default();
for ln in lines.iter() {
match ln.line_type {
LineType::CodeFence => {
if is_inside_fence {
result.push(construct_code_fence(curr_fenced_code, code_fence_option, syntax_highlighter));
curr_fenced_code = vec![];
code_fence_option = CodeFenceOption::default();
}
else {
code_fence_option = parse_code_fence_option(&ln.content);
}
is_inside_fence = !is_inside_fence;
}
_ => {
if is_inside_fence {
curr_fenced_code.push(ln.clone());
}
else {
result.push(ln.clone());
}
}
}
}
if curr_fenced_code.len() > 0 {
result.push(construct_code_fence(curr_fenced_code, code_fence_option, syntax_highlighter));
}
result
}
fn construct_code_fence(mut lines: Vec<Line>, mut options: CodeFenceOption, syntax_highlighter: &SyntaxHighlighter) -> Line {
let mut result = Line {
content: vec![],
indent: 0,
line_type: LineType::FencedCode
};
if lines.len() == 0 {
return result;
}
match options.language {
Some(ref s) => {
lines = syntax_highlighter.highlight_syntax(lines, &String::from_utf16_lossy(&s));
}
_ => {}
}
let code_indent = lines[0].indent;
let mut content = Vec::with_capacity(lines.len() * 3);
let lines_length = lines.len();
for (ind, ln) in lines.iter_mut().enumerate() {
if ind + 1 < lines_length {
content.push(render_line_number(&mut options));
}
if ln.indent > code_indent {
ln.indent -= code_indent;
}
else {
ln.indent = 0;
}
content.push(ln.into_raw());
content.push(vec![U16_NEWLINE]);
}
result.content = content[0..content.len() - 1].concat();
result.content = undo_backslash_escape(&result.content);
result
}
fn render_line_number(options: &mut CodeFenceOption) -> Vec<u16> {
match options.line_num {
None => {vec![]}
Some(ref mut n) => {
*n += 1;
into_v16(&format!("<span class=\"line_no\">{}</span>", (*n - 1)))
}
}
}