atomcode_core/lsp/
registry.rs1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct LspServerConfig {
7 pub command: String,
8 #[serde(default)]
9 pub args: Vec<String>,
10 #[serde(default)]
11 pub root_markers: Vec<String>,
12}
13
14pub struct LspServerRegistry {
15 servers: HashMap<String, LspServerConfig>,
16}
17
18impl LspServerRegistry {
19 pub fn empty() -> Self {
20 Self {
21 servers: HashMap::new(),
22 }
23 }
24
25 pub fn with_defaults() -> Self {
26 let mut servers = HashMap::new();
27 servers.insert(
28 "rs".into(),
29 LspServerConfig {
30 command: "rust-analyzer".into(),
31 args: vec![],
32 root_markers: vec!["Cargo.toml".into()],
33 },
34 );
35 servers.insert(
36 "ts".into(),
37 LspServerConfig {
38 command: "typescript-language-server".into(),
39 args: vec!["--stdio".into()],
40 root_markers: vec!["tsconfig.json".into(), "package.json".into()],
41 },
42 );
43 servers.insert(
44 "tsx".into(),
45 LspServerConfig {
46 command: "typescript-language-server".into(),
47 args: vec!["--stdio".into()],
48 root_markers: vec!["tsconfig.json".into()],
49 },
50 );
51 servers.insert(
52 "js".into(),
53 LspServerConfig {
54 command: "typescript-language-server".into(),
55 args: vec!["--stdio".into()],
56 root_markers: vec!["package.json".into()],
57 },
58 );
59 servers.insert(
60 "py".into(),
61 LspServerConfig {
62 command: "pylsp".into(),
63 args: vec![],
64 root_markers: vec!["pyproject.toml".into(), "setup.py".into()],
65 },
66 );
67 servers.insert(
68 "go".into(),
69 LspServerConfig {
70 command: "gopls".into(),
71 args: vec!["serve".into()],
72 root_markers: vec!["go.mod".into()],
73 },
74 );
75 servers.insert(
76 "java".into(),
77 LspServerConfig {
78 command: "jdtls".into(),
79 args: vec![],
80 root_markers: vec!["pom.xml".into(), "build.gradle".into()],
81 },
82 );
83 Self { servers }
84 }
85
86 pub fn get(&self, extension: &str) -> Option<&LspServerConfig> {
87 self.servers.get(extension)
88 }
89
90 pub fn server_for_file(&self, file_path: &str) -> Option<&LspServerConfig> {
91 let ext = file_path.rsplit('.').next()?;
92 self.servers.get(ext)
93 }
94
95 pub fn merge_user_config(&mut self, user: HashMap<String, LspServerConfig>) {
96 for (ext, config) in user {
97 self.servers.insert(ext, config);
98 }
99 }
100
101 pub fn available_languages(&self) -> Vec<&str> {
102 let mut langs: Vec<_> = self.servers.keys().map(String::as_str).collect();
103 langs.sort();
104 langs
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn defaults_include_common_langs() {
114 let registry = LspServerRegistry::with_defaults();
115 let langs = registry.available_languages();
116 assert!(langs.contains(&"rs"));
117 assert!(langs.contains(&"ts"));
118 assert!(langs.contains(&"py"));
119 assert!(langs.contains(&"go"));
120 assert!(langs.contains(&"java"));
121 assert!(langs.contains(&"js"));
122 assert!(langs.contains(&"tsx"));
123 }
124
125 #[test]
126 fn server_for_file_resolves() {
127 let registry = LspServerRegistry::with_defaults();
128
129 let rust_cfg = registry.server_for_file("src/main.rs").unwrap();
130 assert_eq!(rust_cfg.command, "rust-analyzer");
131
132 let ts_cfg = registry.server_for_file("app/index.ts").unwrap();
133 assert_eq!(ts_cfg.command, "typescript-language-server");
134
135 let py_cfg = registry.server_for_file("script.py").unwrap();
136 assert_eq!(py_cfg.command, "pylsp");
137
138 assert!(registry.server_for_file("data.csv").is_none());
140 }
141
142 #[test]
143 fn user_config_overrides() {
144 let mut registry = LspServerRegistry::with_defaults();
145
146 let mut user = HashMap::new();
148 user.insert(
149 "rs".into(),
150 LspServerConfig {
151 command: "custom-rust-analyzer".into(),
152 args: vec!["--custom".into()],
153 root_markers: vec!["Cargo.toml".into()],
154 },
155 );
156 user.insert(
158 "rb".into(),
159 LspServerConfig {
160 command: "solargraph".into(),
161 args: vec!["stdio".into()],
162 root_markers: vec!["Gemfile".into()],
163 },
164 );
165 registry.merge_user_config(user);
166
167 let rust_cfg = registry.get("rs").unwrap();
168 assert_eq!(rust_cfg.command, "custom-rust-analyzer");
169
170 let ruby_cfg = registry.get("rb").unwrap();
171 assert_eq!(ruby_cfg.command, "solargraph");
172 }
173}