codive_lsp/
language.rs

1//! File extension to language ID mappings
2//!
3//! Maps file extensions to LSP language identifiers as defined in the
4//! [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem).
5
6use std::collections::HashMap;
7use std::path::Path;
8use std::sync::LazyLock;
9
10/// Map of file extensions to LSP language identifiers
11static LANGUAGE_EXTENSIONS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
12    let mut m = HashMap::new();
13
14    // Rust
15    m.insert(".rs", "rust");
16
17    // TypeScript/JavaScript
18    m.insert(".ts", "typescript");
19    m.insert(".tsx", "typescriptreact");
20    m.insert(".js", "javascript");
21    m.insert(".jsx", "javascriptreact");
22    m.insert(".mjs", "javascript");
23    m.insert(".cjs", "javascript");
24    m.insert(".mts", "typescript");
25    m.insert(".cts", "typescript");
26
27    // Python
28    m.insert(".py", "python");
29    m.insert(".pyi", "python");
30    m.insert(".pyw", "python");
31
32    // Go
33    m.insert(".go", "go");
34    m.insert(".mod", "go.mod");
35    m.insert(".sum", "go.sum");
36
37    // Java
38    m.insert(".java", "java");
39
40    // C/C++
41    m.insert(".c", "c");
42    m.insert(".h", "c");
43    m.insert(".cpp", "cpp");
44    m.insert(".cxx", "cpp");
45    m.insert(".cc", "cpp");
46    m.insert(".hpp", "cpp");
47    m.insert(".hxx", "cpp");
48    m.insert(".hh", "cpp");
49
50    // C#
51    m.insert(".cs", "csharp");
52
53    // Ruby
54    m.insert(".rb", "ruby");
55    m.insert(".rake", "ruby");
56    m.insert(".gemspec", "ruby");
57
58    // PHP
59    m.insert(".php", "php");
60
61    // Swift
62    m.insert(".swift", "swift");
63
64    // Kotlin
65    m.insert(".kt", "kotlin");
66    m.insert(".kts", "kotlin");
67
68    // Scala
69    m.insert(".scala", "scala");
70    m.insert(".sc", "scala");
71
72    // Elixir
73    m.insert(".ex", "elixir");
74    m.insert(".exs", "elixir");
75
76    // Erlang
77    m.insert(".erl", "erlang");
78    m.insert(".hrl", "erlang");
79
80    // Haskell
81    m.insert(".hs", "haskell");
82    m.insert(".lhs", "haskell");
83
84    // Lua
85    m.insert(".lua", "lua");
86
87    // Dart
88    m.insert(".dart", "dart");
89
90    // Vue
91    m.insert(".vue", "vue");
92
93    // Svelte
94    m.insert(".svelte", "svelte");
95
96    // HTML/CSS
97    m.insert(".html", "html");
98    m.insert(".htm", "html");
99    m.insert(".css", "css");
100    m.insert(".scss", "scss");
101    m.insert(".sass", "sass");
102    m.insert(".less", "less");
103
104    // JSON/YAML/TOML
105    m.insert(".json", "json");
106    m.insert(".jsonc", "jsonc");
107    m.insert(".yaml", "yaml");
108    m.insert(".yml", "yaml");
109    m.insert(".toml", "toml");
110
111    // Markdown
112    m.insert(".md", "markdown");
113    m.insert(".mdx", "mdx");
114
115    // Shell
116    m.insert(".sh", "shellscript");
117    m.insert(".bash", "shellscript");
118    m.insert(".zsh", "shellscript");
119    m.insert(".fish", "fish");
120
121    // SQL
122    m.insert(".sql", "sql");
123
124    // Zig
125    m.insert(".zig", "zig");
126
127    // Nim
128    m.insert(".nim", "nim");
129
130    // OCaml
131    m.insert(".ml", "ocaml");
132    m.insert(".mli", "ocaml");
133
134    // F#
135    m.insert(".fs", "fsharp");
136    m.insert(".fsi", "fsharp");
137    m.insert(".fsx", "fsharp");
138
139    // Clojure
140    m.insert(".clj", "clojure");
141    m.insert(".cljs", "clojure");
142    m.insert(".cljc", "clojure");
143    m.insert(".edn", "clojure");
144
145    // R
146    m.insert(".r", "r");
147    m.insert(".R", "r");
148
149    // Julia
150    m.insert(".jl", "julia");
151
152    // Terraform
153    m.insert(".tf", "terraform");
154    m.insert(".tfvars", "terraform");
155
156    // Dockerfile
157    m.insert(".dockerfile", "dockerfile");
158
159    // GraphQL
160    m.insert(".graphql", "graphql");
161    m.insert(".gql", "graphql");
162
163    // Protocol Buffers
164    m.insert(".proto", "proto");
165
166    // XML
167    m.insert(".xml", "xml");
168    m.insert(".xsl", "xml");
169    m.insert(".xsd", "xml");
170
171    m
172});
173
174/// Get the LSP language ID for a file path
175pub fn language_id_for_path(path: &Path) -> &'static str {
176    path.extension()
177        .and_then(|ext| ext.to_str())
178        .map(|ext| format!(".{}", ext))
179        .and_then(|ext| LANGUAGE_EXTENSIONS.get(ext.as_str()).copied())
180        .unwrap_or("plaintext")
181}
182
183/// Get the LSP language ID for a file extension (with leading dot)
184pub fn language_id_for_extension(ext: &str) -> &'static str {
185    LANGUAGE_EXTENSIONS.get(ext).copied().unwrap_or("plaintext")
186}
187
188/// Check if a file extension is supported
189pub fn is_supported_extension(ext: &str) -> bool {
190    LANGUAGE_EXTENSIONS.contains_key(ext)
191}
192
193/// Get all supported extensions
194pub fn supported_extensions() -> impl Iterator<Item = &'static str> {
195    LANGUAGE_EXTENSIONS.keys().copied()
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_rust_extension() {
204        assert_eq!(language_id_for_extension(".rs"), "rust");
205    }
206
207    #[test]
208    fn test_typescript_extension() {
209        assert_eq!(language_id_for_extension(".ts"), "typescript");
210        assert_eq!(language_id_for_extension(".tsx"), "typescriptreact");
211    }
212
213    #[test]
214    fn test_python_extension() {
215        assert_eq!(language_id_for_extension(".py"), "python");
216    }
217
218    #[test]
219    fn test_unknown_extension() {
220        assert_eq!(language_id_for_extension(".xyz"), "plaintext");
221    }
222
223    #[test]
224    fn test_language_id_for_path() {
225        let path = Path::new("/some/path/file.rs");
226        assert_eq!(language_id_for_path(path), "rust");
227
228        let path = Path::new("main.py");
229        assert_eq!(language_id_for_path(path), "python");
230    }
231}