use vize_carton::String;
use vize_carton::ToCompactString;
const ANSI_BOLD: &str = "\x1b[1m";
const ANSI_BOLD_OFF: &str = "\x1b[22m";
const ANSI_UNDERLINE: &str = "\x1b[4m";
const ANSI_UNDERLINE_OFF: &str = "\x1b[24m";
const ANSI_CYAN: &str = "\x1b[36m";
const ANSI_CYAN_OFF: &str = "\x1b[39m";
const ANSI_DIM: &str = "\x1b[2m";
const ANSI_DIM_OFF: &str = "\x1b[22m";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HelpRenderTarget {
Ansi,
PlainText,
Markdown,
}
pub fn render_help(markdown: &str, target: HelpRenderTarget) -> String {
match target {
HelpRenderTarget::Ansi => render_markdown_to_ansi(markdown),
HelpRenderTarget::PlainText => strip_markdown(markdown),
HelpRenderTarget::Markdown => markdown.to_compact_string(),
}
}
pub(crate) fn strip_markdown_first_line(text: &str) -> String {
let mut in_code_block = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("```") {
in_code_block = !in_code_block;
continue;
}
if in_code_block {
continue;
}
if trimmed.is_empty() {
continue;
}
let stripped = trimmed.replace("**", "").replace("__", "").replace('`', "");
let stripped = stripped.trim_start_matches('#').trim();
if stripped.is_empty() {
continue;
}
return stripped.to_compact_string();
}
text.lines().next().unwrap_or(text).to_compact_string()
}
pub(crate) fn render_markdown_to_ansi(text: &str) -> String {
let mut result = String::with_capacity(text.len() + 64);
let mut in_code_block = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("```") {
in_code_block = !in_code_block;
continue;
}
if !result.is_empty() {
result.push('\n');
}
if in_code_block {
result.push_str(ANSI_DIM);
result.push_str(" ");
result.push_str(line);
result.push_str(ANSI_DIM_OFF);
continue;
}
if let Some(header_content) = trimmed.strip_prefix("# ") {
result.push_str(ANSI_BOLD);
result.push_str(ANSI_UNDERLINE);
result.push_str(header_content);
result.push_str(ANSI_UNDERLINE_OFF);
result.push_str(ANSI_BOLD_OFF);
continue;
}
if let Some(header_content) = trimmed.strip_prefix("## ") {
result.push_str(ANSI_BOLD);
result.push_str(ANSI_UNDERLINE);
result.push_str(header_content);
result.push_str(ANSI_UNDERLINE_OFF);
result.push_str(ANSI_BOLD_OFF);
continue;
}
if let Some(header_content) = trimmed.strip_prefix("### ") {
result.push_str(ANSI_BOLD);
result.push_str(header_content);
result.push_str(ANSI_BOLD_OFF);
continue;
}
render_inline_markdown(&mut result, line);
}
result
}
fn render_inline_markdown(out: &mut String, line: &str) {
let bytes = line.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == b'`' {
if let Some(end) = find_closing_backtick(bytes, i + 1) {
out.push_str(ANSI_CYAN);
out.push_str(&line[i + 1..end]);
out.push_str(ANSI_CYAN_OFF);
i = end + 1;
continue;
}
}
if i + 1 < len && bytes[i] == b'*' && bytes[i + 1] == b'*' {
if let Some(end) = find_closing_double(bytes, i + 2, b'*') {
out.push_str(ANSI_BOLD);
render_inline_markdown(out, &line[i + 2..end]);
out.push_str(ANSI_BOLD_OFF);
i = end + 2;
continue;
}
}
if i + 1 < len && bytes[i] == b'_' && bytes[i + 1] == b'_' {
if let Some(end) = find_closing_double(bytes, i + 2, b'_') {
out.push_str(ANSI_BOLD);
render_inline_markdown(out, &line[i + 2..end]);
out.push_str(ANSI_BOLD_OFF);
i = end + 2;
continue;
}
}
out.push(bytes[i] as char);
i += 1;
}
}
fn find_closing_backtick(bytes: &[u8], start: usize) -> Option<usize> {
(start..bytes.len()).find(|&i| bytes[i] == b'`')
}
fn find_closing_double(bytes: &[u8], start: usize, ch: u8) -> Option<usize> {
let mut i = start;
while i + 1 < bytes.len() {
if bytes[i] == ch && bytes[i + 1] == ch {
return Some(i);
}
i += 1;
}
None
}
pub(crate) fn strip_markdown(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut in_code_block = false;
for line in text.lines() {
let trimmed = line.trim();
if trimmed.starts_with("```") {
in_code_block = !in_code_block;
continue;
}
if in_code_block {
if !result.is_empty() {
result.push('\n');
}
result.push_str(" ");
result.push_str(trimmed);
continue;
}
if trimmed.is_empty() {
if !result.is_empty() && !result.ends_with('\n') {
result.push('\n');
}
continue;
}
if !result.is_empty() && !result.ends_with('\n') {
result.push('\n');
}
let content = trimmed
.strip_prefix("### ")
.or_else(|| trimmed.strip_prefix("## "))
.or_else(|| trimmed.strip_prefix("# "))
.unwrap_or(trimmed);
let content = content.replace("**", "").replace("__", "").replace('`', "");
result.push_str(content.trim());
}
let trimmed = result.trim_end();
trimmed.to_compact_string()
}