Skip to main content

apple_code_assistant/utils/
syntax_highlight.rs

1//! Syntax highlighting with syntect (ANSI output for terminal)
2
3use syntect::easy::HighlightLines;
4use syntect::highlighting::ThemeSet;
5use syntect::parsing::SyntaxSet;
6use syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings};
7
8/// Map language name to syntect extension for syntax lookup.
9fn lang_to_extension(lang: &str) -> &str {
10    match lang.to_lowercase().as_str() {
11        "rust" => "rs",
12        "typescript" | "ts" => "ts",
13        "javascript" | "js" => "js",
14        "python" | "py" => "py",
15        "go" => "go",
16        "java" => "java",
17        "c" => "c",
18        "cpp" | "c++" => "cpp",
19        "csharp" | "c#" => "cs",
20        "swift" => "swift",
21        "kotlin" => "kt",
22        "ruby" => "rb",
23        "php" => "php",
24        "html" => "html",
25        "css" => "css",
26        "sql" => "sql",
27        "shell" | "bash" | "sh" => "sh",
28        "json" => "json",
29        "yaml" | "yml" => "yml",
30        "markdown" | "md" => "md",
31        _ => "txt",
32    }
33}
34
35/// Highlight code to ANSI-colored string for terminal. Returns original code if no syntax found.
36pub fn highlight_to_ansi(code: &str, language: Option<&str>, dark: bool) -> String {
37    let ps = SyntaxSet::load_defaults_newlines();
38    let ts = ThemeSet::load_defaults();
39    let theme_name = if dark {
40        "base16-ocean.dark"
41    } else {
42        "base16-ocean.light"
43    };
44    let theme = match ts.themes.get(theme_name) {
45        Some(t) => t,
46        None => return code.to_string(),
47    };
48    let ext = language.map(lang_to_extension).unwrap_or("txt");
49    let syntax = match ps.find_syntax_by_extension(ext) {
50        Some(s) => s,
51        None => return code.to_string(),
52    };
53    let mut highlighter = HighlightLines::new(syntax, theme);
54    let mut out = String::new();
55    for line in LinesWithEndings::from(code) {
56        if let Ok(ranges) = highlighter.highlight_line(line, &ps) {
57            out.push_str(&as_24_bit_terminal_escaped(&ranges[..], true));
58        } else {
59            out.push_str(line);
60        }
61        out.push('\n');
62    }
63    out
64}