codive-lsp 0.1.0

LSP client infrastructure for Codive
Documentation
//! File extension to language ID mappings
//!
//! Maps file extensions to LSP language identifiers as defined in the
//! [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem).

use std::collections::HashMap;
use std::path::Path;
use std::sync::LazyLock;

/// Map of file extensions to LSP language identifiers
static LANGUAGE_EXTENSIONS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
    let mut m = HashMap::new();

    // Rust
    m.insert(".rs", "rust");

    // TypeScript/JavaScript
    m.insert(".ts", "typescript");
    m.insert(".tsx", "typescriptreact");
    m.insert(".js", "javascript");
    m.insert(".jsx", "javascriptreact");
    m.insert(".mjs", "javascript");
    m.insert(".cjs", "javascript");
    m.insert(".mts", "typescript");
    m.insert(".cts", "typescript");

    // Python
    m.insert(".py", "python");
    m.insert(".pyi", "python");
    m.insert(".pyw", "python");

    // Go
    m.insert(".go", "go");
    m.insert(".mod", "go.mod");
    m.insert(".sum", "go.sum");

    // Java
    m.insert(".java", "java");

    // C/C++
    m.insert(".c", "c");
    m.insert(".h", "c");
    m.insert(".cpp", "cpp");
    m.insert(".cxx", "cpp");
    m.insert(".cc", "cpp");
    m.insert(".hpp", "cpp");
    m.insert(".hxx", "cpp");
    m.insert(".hh", "cpp");

    // C#
    m.insert(".cs", "csharp");

    // Ruby
    m.insert(".rb", "ruby");
    m.insert(".rake", "ruby");
    m.insert(".gemspec", "ruby");

    // PHP
    m.insert(".php", "php");

    // Swift
    m.insert(".swift", "swift");

    // Kotlin
    m.insert(".kt", "kotlin");
    m.insert(".kts", "kotlin");

    // Scala
    m.insert(".scala", "scala");
    m.insert(".sc", "scala");

    // Elixir
    m.insert(".ex", "elixir");
    m.insert(".exs", "elixir");

    // Erlang
    m.insert(".erl", "erlang");
    m.insert(".hrl", "erlang");

    // Haskell
    m.insert(".hs", "haskell");
    m.insert(".lhs", "haskell");

    // Lua
    m.insert(".lua", "lua");

    // Dart
    m.insert(".dart", "dart");

    // Vue
    m.insert(".vue", "vue");

    // Svelte
    m.insert(".svelte", "svelte");

    // HTML/CSS
    m.insert(".html", "html");
    m.insert(".htm", "html");
    m.insert(".css", "css");
    m.insert(".scss", "scss");
    m.insert(".sass", "sass");
    m.insert(".less", "less");

    // JSON/YAML/TOML
    m.insert(".json", "json");
    m.insert(".jsonc", "jsonc");
    m.insert(".yaml", "yaml");
    m.insert(".yml", "yaml");
    m.insert(".toml", "toml");

    // Markdown
    m.insert(".md", "markdown");
    m.insert(".mdx", "mdx");

    // Shell
    m.insert(".sh", "shellscript");
    m.insert(".bash", "shellscript");
    m.insert(".zsh", "shellscript");
    m.insert(".fish", "fish");

    // SQL
    m.insert(".sql", "sql");

    // Zig
    m.insert(".zig", "zig");

    // Nim
    m.insert(".nim", "nim");

    // OCaml
    m.insert(".ml", "ocaml");
    m.insert(".mli", "ocaml");

    // F#
    m.insert(".fs", "fsharp");
    m.insert(".fsi", "fsharp");
    m.insert(".fsx", "fsharp");

    // Clojure
    m.insert(".clj", "clojure");
    m.insert(".cljs", "clojure");
    m.insert(".cljc", "clojure");
    m.insert(".edn", "clojure");

    // R
    m.insert(".r", "r");
    m.insert(".R", "r");

    // Julia
    m.insert(".jl", "julia");

    // Terraform
    m.insert(".tf", "terraform");
    m.insert(".tfvars", "terraform");

    // Dockerfile
    m.insert(".dockerfile", "dockerfile");

    // GraphQL
    m.insert(".graphql", "graphql");
    m.insert(".gql", "graphql");

    // Protocol Buffers
    m.insert(".proto", "proto");

    // XML
    m.insert(".xml", "xml");
    m.insert(".xsl", "xml");
    m.insert(".xsd", "xml");

    m
});

/// Get the LSP language ID for a file path
pub fn language_id_for_path(path: &Path) -> &'static str {
    path.extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| format!(".{}", ext))
        .and_then(|ext| LANGUAGE_EXTENSIONS.get(ext.as_str()).copied())
        .unwrap_or("plaintext")
}

/// Get the LSP language ID for a file extension (with leading dot)
pub fn language_id_for_extension(ext: &str) -> &'static str {
    LANGUAGE_EXTENSIONS.get(ext).copied().unwrap_or("plaintext")
}

/// Check if a file extension is supported
pub fn is_supported_extension(ext: &str) -> bool {
    LANGUAGE_EXTENSIONS.contains_key(ext)
}

/// Get all supported extensions
pub fn supported_extensions() -> impl Iterator<Item = &'static str> {
    LANGUAGE_EXTENSIONS.keys().copied()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rust_extension() {
        assert_eq!(language_id_for_extension(".rs"), "rust");
    }

    #[test]
    fn test_typescript_extension() {
        assert_eq!(language_id_for_extension(".ts"), "typescript");
        assert_eq!(language_id_for_extension(".tsx"), "typescriptreact");
    }

    #[test]
    fn test_python_extension() {
        assert_eq!(language_id_for_extension(".py"), "python");
    }

    #[test]
    fn test_unknown_extension() {
        assert_eq!(language_id_for_extension(".xyz"), "plaintext");
    }

    #[test]
    fn test_language_id_for_path() {
        let path = Path::new("/some/path/file.rs");
        assert_eq!(language_id_for_path(path), "rust");

        let path = Path::new("main.py");
        assert_eq!(language_id_for_path(path), "python");
    }
}