cortex-agent 0.2.1

Self-learning AI agent with persistent memory, tools, plugins, and a beautiful terminal UI
use console::style;

/// Render markdown text to terminal with clean formatting.
/// Supports: `**bold**`, `*italic*`, `` `code` ``, ```code blocks```, `> quotes`, `- lists`, `# headers`
/// No heavy box borders — uses clean indentation and color only.
#[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 {
                // End of code block — blank line for spacing
                println!();
            }
            continue;
        }

        if in_code_block {
            // Code block lines: colored indent, no box chars
            if line.is_empty() {
                println!();
            } else {
                println!("  {}", style(line).yellow());
            }
            continue;
        }

        let trimmed = line.trim();
        if trimmed.is_empty() {
            println!();
            continue;
        }

        // Headers
        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;
        }

        // Blockquotes
        if trimmed.starts_with('>') {
            let content = trimmed.trim_start_matches('>').trim();
            println!(" {} {}", style("").dim(), style(content).italic());
            continue;
        }

        // Unordered lists
        if trimmed.starts_with("- ") || trimmed.starts_with("* ") {
            let content = &trimmed[2..];
            println!("  {} {}", style("").cyan(), inline_markdown(content));
            continue;
        }

        // Ordered lists
        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;
            }
        }

        // Horizontal rule
        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;
        }

        // Regular paragraph with inline formatting
        println!("{}", inline_markdown(line));
    }
}

/// Render inline markdown formatting within a 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 {
            '`' => {
                // Inline code: text between backticks
                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(&'*') {
                    // Bold: **text**
                    chars.next(); // consume second *
                    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 {
                    // Italic: *text*
                    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()));
                    }
                }
            }
            '[' => {
                // Link: [text](url)
                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(); // consume (
                    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"));
    }
}