giallo-kak 0.2.2

Kakoune syntax highlighter using TextMate grammars
use giallo::Registry;
use std::fs;

use crate::config::{expand_path, Config};

pub fn list_grammars(registry: &Registry, config: &Config, plain: bool) {
    if !plain {
        println!("Available grammars:");
        println!();
    }

    let common_grammars = vec![
        "rust",
        "python",
        "javascript",
        "typescript",
        "json",
        "yaml",
        "toml",
        "markdown",
        "bash",
        "go",
        "cpp",
        "c",
        "java",
        "ruby",
        "php",
        "html",
        "css",
        "scss",
        "xml",
        "sql",
        "docker",
        "terraform",
        "hcl",
        "shellscript",
        "lua",
        "vim",
        "regex",
        "make",
        "cmake",
        "ini",
        "diff",
        "git-commit",
        "git-rebase",
        "graphql",
        "proto",
        "swift",
        "kotlin",
        "scala",
        "clojure",
        "erlang",
        "elixir",
        "haskell",
        "ocaml",
        "fsharp",
        "r",
        "matlab",
        "julia",
        "perl",
    ];

    let mut found_grammars = Vec::new();
    for grammar in &common_grammars {
        if registry.contains_grammar(grammar) {
            found_grammars.push(*grammar);
        }
    }

    let mut custom_grammars: Vec<String> = Vec::new();
    if let Some(ref grammars_path) = config.grammars_path {
        let path = expand_path(grammars_path);
        if path.exists() {
            if let Ok(entries) = fs::read_dir(&path) {
                custom_grammars = entries
                    .filter_map(|e| e.ok())
                    .filter(|e| {
                        let name = e.file_name();
                        let name_str = name.to_string_lossy();
                        !name_str.starts_with('.') && e.path().is_file()
                    })
                    .filter_map(|e| {
                        let path = e.path();
                        let ext = path.extension().and_then(|e| e.to_str());
                        if ext == Some("json") || ext == Some("plist") {
                            path.file_stem().map(|s| s.to_string_lossy().to_string())
                        } else {
                            None
                        }
                    })
                    .collect();
                custom_grammars.sort();
            }
        }
    }

    if plain {
        for grammar in &found_grammars {
            println!("{}", grammar);
        }
        for grammar in &custom_grammars {
            println!("{}", grammar);
        }
    } else {
        if !found_grammars.is_empty() {
            println!("Builtin grammars ({}):", found_grammars.len());
            for grammar in &found_grammars {
                println!("  {}", grammar);
            }
            println!();
        }

        if !custom_grammars.is_empty() {
            if let Some(ref grammars_path) = config.grammars_path {
                println!(
                    "Custom grammars from {} ({}):",
                    grammars_path,
                    custom_grammars.len()
                );
                for grammar in &custom_grammars {
                    println!("  {} (custom)", grammar);
                }
                println!();
            }
        }

        if found_grammars.is_empty() && custom_grammars.is_empty() {
            println!("  No grammars found.");
        }

        println!("Use in config.toml:");
        println!("  [language_map]");
        println!("  <filetype> = \"<grammar_id>\"");
        println!();
        println!("Or in Kakoune:");
        println!("  set-option buffer giallo_lang <grammar_id>");
    }
}

pub fn list_themes(registry: &Registry, config: &Config, plain: bool) {
    if !plain {
        println!("Available themes:");
        println!();
    }

    let common_themes = vec![
        "catppuccin-frappe",
        "catppuccin-latte",
        "catppuccin-macchiato",
        "catppuccin-mocha",
        "dracula",
        "dracula-soft",
        "gruvbox-dark-hard",
        "gruvbox-dark-medium",
        "gruvbox-dark-soft",
        "gruvbox-light-hard",
        "gruvbox-light-medium",
        "gruvbox-light-soft",
        "kanagawa-dragon",
        "kanagawa-lotus",
        "kanagawa-wave",
        "tokyo-night",
        "github-dark",
        "github-dark-default",
        "github-dark-dimmed",
        "github-light",
        "github-light-default",
        "monokai",
        "nord",
        "one-dark-pro",
        "rose-pine",
        "rose-pine-dawn",
        "rose-pine-moon",
        "solarized-dark",
        "solarized-light",
        "ayu-dark",
        "ayu-mirage",
        "vscode-dark",
        "dark-plus",
        "light-plus",
    ];

    let mut found_themes = Vec::new();
    for theme in &common_themes {
        if registry.contains_theme(theme) {
            found_themes.push(*theme);
        }
    }

    let mut custom_themes: Vec<String> = Vec::new();
    if let Some(ref themes_path) = config.themes_path {
        let path = expand_path(themes_path);
        if path.exists() {
            if let Ok(entries) = fs::read_dir(&path) {
                custom_themes = entries
                    .filter_map(|e| e.ok())
                    .filter(|e| {
                        let name = e.file_name();
                        let name_str = name.to_string_lossy();
                        !name_str.starts_with('.') && e.path().is_file()
                    })
                    .filter_map(|e| {
                        let path = e.path();
                        let ext = path.extension().and_then(|e| e.to_str());
                        if ext == Some("json") {
                            path.file_stem().map(|s| s.to_string_lossy().to_string())
                        } else {
                            None
                        }
                    })
                    .collect();
                custom_themes.sort();
            }
        }
    }

    if plain {
        for theme in &found_themes {
            println!("{}", theme);
        }
        for theme in &custom_themes {
            println!("{}", theme);
        }
    } else {
        if !found_themes.is_empty() {
            println!("Builtin themes ({}):", found_themes.len());
            for theme in &found_themes {
                println!("  {}", theme);
            }
            println!();
        }

        if !custom_themes.is_empty() {
            if let Some(ref themes_path) = config.themes_path {
                println!(
                    "Custom themes from {} ({}):",
                    themes_path,
                    custom_themes.len()
                );
                for theme in &custom_themes {
                    println!("  {} (custom)", theme);
                }
                println!();
            }
        }

        if found_themes.is_empty() && custom_themes.is_empty() {
            println!("  No themes found.");
        }

        println!("Use in config.toml:");
        println!("  theme = \"<theme_name>\"");
        println!();
        println!("Or in Kakoune:");
        println!("  giallo-set-theme <theme_name>");
    }
}