use console::style;
#[allow(dead_code)]
pub fn render_markdown(text: &str) {
let mut in_code_block = false;
for line in text.lines() {
if line.starts_with("```") {
in_code_block = !in_code_block;
if !in_code_block {
println!();
}
continue;
}
if in_code_block {
if line.is_empty() {
println!();
} else {
println!(" {}", style(line).yellow());
}
continue;
}
let trimmed = line.trim();
if trimmed.is_empty() {
println!();
continue;
}
if let Some(rest) = trimmed.strip_prefix("### ") {
println!(" {}", style(rest).bold().dim());
continue;
}
if let Some(rest) = trimmed.strip_prefix("## ") {
println!(" {}", style(rest).bold().white());
continue;
}
if let Some(rest) = trimmed.strip_prefix("# ") {
println!(" {}", style(rest).bold().white().underlined());
continue;
}
if trimmed.starts_with('>') {
let content = trimmed.trim_start_matches('>').trim();
println!(" {} {}", style("▎").dim(), style(content).italic());
continue;
}
if trimmed.starts_with("- ") || trimmed.starts_with("* ") {
let content = &trimmed[2..];
println!(" {} {}", style("•").cyan(), inline_markdown(content));
continue;
}
if let Some((num, content)) = trimmed.split_once(". ").and_then(|(n, r)| {
if n.chars().all(|c| c.is_ascii_digit()) { Some((n, r)) } else { None }
}) {
if trimmed.starts_with(num) && trimmed.get(num.len()..).map_or(false, |s| s.starts_with(". ")) {
println!(" {} {}", style(format!("{}.", num)).cyan(), inline_markdown(content));
continue;
}
}
if trimmed.chars().all(|c| c == '-' || c == '*' || c == '_') && trimmed.len() >= 3 {
println!(" {}", style("─".repeat(terminal_size::terminal_size().map(|(w, _)| w.0 as usize).unwrap_or(72).min(60))).dim());
continue;
}
println!("{}", inline_markdown(line));
}
}
#[allow(dead_code)]
fn inline_markdown(text: &str) -> String {
let mut result = String::new();
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
match c {
'`' => {
let mut code = String::new();
while let Some(&next) = chars.peek() {
if next == '`' { chars.next(); break; }
code.push(next); chars.next();
}
result.push_str(&format!("{}", style(&code).yellow().bold()));
}
'*' => {
if chars.peek() == Some(&'*') {
chars.next(); let mut bold = String::new();
while let Some(&next) = chars.peek() {
if next == '*' { chars.next(); if chars.peek() == Some(&'*') { chars.next(); break; } else { bold.push('*'); continue; } }
bold.push(next); chars.next();
}
result.push_str(&format!("{}", style(&bold).bold()));
} else {
let mut italic = String::new();
while let Some(&next) = chars.peek() {
if next == '*' { chars.next(); break; }
italic.push(next); chars.next();
}
if italic.is_empty() {
result.push('*');
} else {
result.push_str(&format!("{}", style(&italic).italic()));
}
}
}
'[' => {
let mut link_text = String::new();
while let Some(&next) = chars.peek() {
if next == ']' { chars.next(); break; }
link_text.push(next); chars.next();
}
if chars.peek() == Some(&'(') {
chars.next(); let mut url = String::new();
while let Some(&next) = chars.peek() {
if next == ')' { chars.next(); break; }
url.push(next); chars.next();
}
result.push_str(&format!("{}", style(link_text).underlined().cyan()));
} else {
result.push_str(&format!("[{}", link_text));
}
}
_ => result.push(c),
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bold() {
let result = inline_markdown("hello **world** test");
assert!(result.contains("world"));
assert!(result.contains("hello"));
assert!(result.contains("test"));
}
#[test]
fn test_italic() {
let result = inline_markdown("hello *world* test");
assert!(result.contains("world"));
}
#[test]
fn test_code() {
let result = inline_markdown("use `cortex` command");
assert!(result.contains("cortex"));
}
#[test]
fn test_link() {
let result = inline_markdown("[click here](https://example.com)");
assert!(result.contains("click here"));
}
}