返回 DeepSeek-TUI-2026
registry.rs
根目录 / crates / tui / src / lsp / registry.rs
1 //! Language detection + the fixed dictionary mapping a language to the LSP
2 //! server binary that handles it.
3 //!
4 //! Kept intentionally small: a dozen languages, a hard-coded executable name
5 //! per language, an optional list of args. Users can override the defaults
6 //! via `[lsp.servers]` in `~/.deepseek/config.toml` (handled by
7 //! [`super::LspConfig`], not this file).
8
9 use std::path::Path;
10
11 /// A language we know how to ask an LSP server about. Detected from the file
12 /// extension by [`detect_language`]. `Other` is a sentinel used when we do
13 /// not have an LSP for the file — the LSP manager treats it as "skip".
14 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15 pub enum Language {
16 Rust,
17 Go,
18 Python,
19 TypeScript,
20 JavaScript,
21 C,
22 Cpp,
23 Other,
24 }
25
26 impl Language {
27 /// Stable lowercase string used as the key in `[lsp.servers]` overrides
28 /// and in log lines.
29 #[must_use]
30 pub fn as_key(self) -> &'static str {
31 match self {
32 Language::Rust => "rust",
33 Language::Go => "go",
34 Language::Python => "python",
35 Language::TypeScript => "typescript",
36 Language::JavaScript => "javascript",
37 Language::C => "c",
38 Language::Cpp => "cpp",
39 Language::Other => "other",
40 }
41 }
42
43 /// LSP `languageId` value used in `textDocument/didOpen`. We follow the
44 /// LSP-spec values: `rust`, `go`, `python`, `typescript`, `javascript`,
45 /// `c`, `cpp`.
46 #[must_use]
47 pub fn language_id(self) -> &'static str {
48 match self {
49 Language::Rust => "rust",
50 Language::Go => "go",
51 Language::Python => "python",
52 Language::TypeScript => "typescript",
53 Language::JavaScript => "javascript",
54 Language::C => "c",
55 Language::Cpp => "cpp",
56 Language::Other => "plaintext",
57 }
58 }
59 }
60
61 /// Detect the language of `path` from its extension. Falls back to
62 /// `Language::Other` when the extension is unknown (or the file has none),
63 /// which signals "skip" to the manager.
64 #[must_use]
65 pub fn detect_language(path: &Path) -> Language {
66 let ext = match path.extension().and_then(|e| e.to_str()) {
67 Some(ext) => ext.to_ascii_lowercase(),
68 None => return Language::Other,
69 };
70 match ext.as_str() {
71 "rs" => Language::Rust,
72 "go" => Language::Go,
73 "py" | "pyi" => Language::Python,
74 "ts" | "tsx" => Language::TypeScript,
75 "js" | "jsx" | "mjs" | "cjs" => Language::JavaScript,
76 "c" | "h" => Language::C,
77 "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "hh" => Language::Cpp,
78 _ => Language::Other,
79 }
80 }
81
82 /// Fixed default for "what executable + args do we run for `lang`?".
83 /// Returns `None` when no LSP server is wired for that language. The TUI
84 /// config layer can override this dictionary at runtime.
85 #[must_use]
86 pub fn server_for(lang: Language) -> Option<(&'static str, &'static [&'static str])> {
87 match lang {
88 Language::Rust => Some(("rust-analyzer", &[])),
89 Language::Go => Some(("gopls", &["serve"])),
90 Language::Python => Some(("pyright-langserver", &["--stdio"])),
91 Language::TypeScript | Language::JavaScript => {
92 Some(("typescript-language-server", &["--stdio"]))
93 }
94 Language::C | Language::Cpp => Some(("clangd", &[])),
95 Language::Other => None,
96 }
97 }
98
99 #[cfg(test)]
100 mod tests {
101 use super::*;
102 use std::path::PathBuf;
103
104 #[test]
105 fn detects_rust_extension() {
106 assert_eq!(detect_language(&PathBuf::from("foo.rs")), Language::Rust);
107 assert_eq!(detect_language(&PathBuf::from("FOO.RS")), Language::Rust);
108 }
109
110 #[test]
111 fn detects_unknown_as_other() {
112 assert_eq!(
113 detect_language(&PathBuf::from("notes.txt")),
114 Language::Other
115 );
116 assert_eq!(detect_language(&PathBuf::from("README")), Language::Other);
117 }
118
119 #[test]
120 fn detects_typescript_variants() {
121 assert_eq!(
122 detect_language(&PathBuf::from("foo.ts")),
123 Language::TypeScript
124 );
125 assert_eq!(
126 detect_language(&PathBuf::from("foo.tsx")),
127 Language::TypeScript
128 );
129 assert_eq!(
130 detect_language(&PathBuf::from("foo.js")),
131 Language::JavaScript
132 );
133 }
134
135 #[test]
136 fn server_for_rust_is_rust_analyzer() {
137 let (cmd, args) = server_for(Language::Rust).expect("rust has a server");
138 assert_eq!(cmd, "rust-analyzer");
139 assert!(args.is_empty());
140 }
141
142 #[test]
143 fn server_for_other_is_none() {
144 assert!(server_for(Language::Other).is_none());
145 }
146 }
147
147 lines RUST