mod constants;
mod inline;
mod renderer;
mod table;
mod wrap;
use crate::ParseOptions;
use crate::inline::InlineBuffers;
use renderer::AnsiRenderer;
#[derive(Clone, Debug)]
pub struct AnsiOptions {
pub width: usize,
pub color: bool,
pub line_numbers: bool,
pub padding: usize,
}
impl Default for AnsiOptions {
fn default() -> Self {
Self {
width: 80,
color: true,
line_numbers: false,
padding: 0,
}
}
}
pub fn render_ansi_terminal(
markdown: &str,
options: &ParseOptions,
aopts: Option<&AnsiOptions>,
) -> String {
let default_aopts;
let aopts = match aopts {
Some(a) => a,
None => {
default_aopts = AnsiOptions::default();
&default_aopts
}
};
let markdown = if options.max_input_size > 0 && markdown.len() > options.max_input_size {
let mut end = options.max_input_size;
while end > 0 && !markdown.is_char_boundary(end) {
end -= 1;
}
&markdown[..end]
} else {
markdown
};
let mut parser = crate::block::BlockParser::new(markdown, options);
let doc = parser.parse();
let refs = parser.ref_defs;
let mut out = String::with_capacity(markdown.len() * 2);
let mut bufs = InlineBuffers::new();
let mut renderer = AnsiRenderer {
refs: &refs,
opts: options,
aopts,
bufs: &mut bufs,
out: &mut out,
list_depth: 0,
list_counters: Vec::new(),
prev_was_heading: false,
};
renderer.render_block(&doc);
if aopts.padding > 0 {
pad_ansi_output(out, aopts.padding)
} else {
out
}
}
fn pad_ansi_output(text: String, padding: usize) -> String {
if padding == 0 || text.is_empty() {
return text;
}
let pad = " ".repeat(padding);
let estimated_lines = text.len() / 60 + 1;
let top_padding_lines = padding.div_ceil(2);
let mut out =
String::with_capacity(text.len() + padding * 2 * estimated_lines + top_padding_lines);
for _ in 0..top_padding_lines {
out.push('\n');
}
for segment in text.split_inclusive('\n') {
let (line, has_newline) = match segment.strip_suffix('\n') {
Some(stripped) => (stripped, true),
None => (segment, false),
};
out.push_str(&pad);
out.push_str(line);
out.push_str(&pad);
if has_newline {
out.push('\n');
}
}
out
}