1use super::LspError;
6use super::LspResult;
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::process::{Child, Command, Stdio};
10use std::sync::Arc;
11use tokio::sync::Mutex;
12
13#[derive(Debug, Clone)]
15pub struct LanguageServerConfig {
16 pub name: String,
18 pub command: String,
20 pub args: Vec<String>,
22 pub extensions: Vec<String>,
24 pub init_timeout_secs: u64,
26}
27
28impl LanguageServerConfig {
29 pub fn new(
30 name: impl Into<String>,
31 command: impl Into<String>,
32 args: Vec<String>,
33 extensions: Vec<String>,
34 ) -> Self {
35 Self {
36 name: name.into(),
37 command: command.into(),
38 args,
39 extensions,
40 init_timeout_secs: 30,
41 }
42 }
43}
44
45#[derive(Debug)]
47pub struct LanguageServer {
48 pub config: LanguageServerConfig,
50 pub process: Option<Child>,
52 pub root_path: PathBuf,
54}
55
56impl LanguageServer {
57 pub fn new(config: LanguageServerConfig, root_path: PathBuf) -> LspResult<Self> {
59 Ok(Self {
60 config,
61 process: None,
62 root_path,
63 })
64 }
65
66 pub fn start(&mut self) -> LspResult<()> {
68 let mut command = Command::new(&self.config.command);
69
70 command.current_dir(&self.root_path);
72
73 for arg in &self.config.args {
75 command.arg(arg);
76 }
77
78 command
80 .stdin(Stdio::piped())
81 .stdout(Stdio::piped())
82 .stderr(Stdio::piped());
83
84 let child = command.spawn().map_err(|e| {
85 LspError::ServerCrashed(format!("Failed to start {}: {}", self.config.name, e))
86 })?;
87
88 self.process = Some(child);
89 tracing::info!("Started LSP server: {}", self.config.name);
90
91 Ok(())
92 }
93
94 pub fn stop(&mut self) -> LspResult<()> {
96 if let Some(mut process) = self.process.take() {
97 let _ = process.kill();
98 tracing::info!("Stopped LSP server: {}", self.config.name);
99 }
100 Ok(())
101 }
102
103 pub fn config(&self) -> &LanguageServerConfig {
105 &self.config
106 }
107
108 pub fn stdin(&self) -> Option<&std::process::ChildStdin> {
110 self.process.as_ref().and_then(|p| p.stdin.as_ref())
111 }
112
113 pub fn stdout(&self) -> Option<&std::process::ChildStdout> {
115 self.process.as_ref().and_then(|p| p.stdout.as_ref())
116 }
117
118 pub fn is_running(&self) -> bool {
120 self.process.is_some()
121 }
122
123 pub fn root_path(&self) -> &PathBuf {
125 &self.root_path
126 }
127}
128
129impl Drop for LanguageServer {
130 fn drop(&mut self) {
131 let _ = self.stop();
132 }
133}
134
135pub struct LanguageServerManager {
137 pub servers: Arc<Mutex<HashMap<String, LanguageServer>>>,
139 configs: HashMap<String, LanguageServerConfig>,
141}
142
143impl LanguageServerManager {
144 pub fn new() -> Self {
146 let mut configs = HashMap::new();
147
148 configs.insert(
150 "rust".to_string(),
151 LanguageServerConfig::new(
152 "rust-analyzer",
153 "rust-analyzer",
154 vec![],
155 vec!["rs".to_string()],
156 ),
157 );
158
159 configs.insert(
161 "python".to_string(),
162 LanguageServerConfig::new(
163 "pyright",
164 "pyright-langserver",
165 vec!["--stdio".to_string()],
166 vec!["py".to_string()],
167 ),
168 );
169
170 configs.insert(
172 "typescript".to_string(),
173 LanguageServerConfig::new(
174 "typescript-language-server",
175 "typescript-language-server",
176 vec!["--stdio".to_string()],
177 vec![
178 "ts".to_string(),
179 "tsx".to_string(),
180 "js".to_string(),
181 "jsx".to_string(),
182 ],
183 ),
184 );
185
186 configs.insert(
188 "go".to_string(),
189 LanguageServerConfig::new(
190 "gopls",
191 "gopls",
192 vec!["serve".to_string()],
193 vec!["go".to_string()],
194 ),
195 );
196
197 configs.insert(
199 "cpp".to_string(),
200 LanguageServerConfig::new(
201 "clangd",
202 "clangd",
203 vec!["--background-index".to_string()],
204 vec![
205 "c".to_string(),
206 "cpp".to_string(),
207 "h".to_string(),
208 "hpp".to_string(),
209 ],
210 ),
211 );
212
213 Self {
214 servers: Arc::new(Mutex::new(HashMap::new())),
215 configs,
216 }
217 }
218
219 pub fn get_language_from_extension(ext: &str) -> Option<&'static str> {
221 match ext {
222 "rs" => Some("rust"),
223 "py" => Some("python"),
224 "ts" | "tsx" => Some("typescript"),
225 "js" | "jsx" => Some("typescript"), "go" => Some("go"),
227 "c" | "cpp" | "h" | "hpp" => Some("cpp"),
228 _ => None,
229 }
230 }
231
232 pub fn get_config(&self, language: &str) -> Option<&LanguageServerConfig> {
234 self.configs.get(language)
235 }
236
237 pub async fn start_server(&self, language: &str, root_path: PathBuf) -> LspResult<()> {
239 let config = self
240 .configs
241 .get(language)
242 .ok_or_else(|| LspError::ServerNotFound(language.to_string()))?
243 .clone();
244
245 let mut server = LanguageServer::new(config, root_path)?;
246 server.start()?;
247
248 let mut servers = self.servers.lock().await;
249 servers.insert(language.to_string(), server);
250
251 Ok(())
252 }
253
254 pub async fn get_server(&self, language: &str) -> Option<LanguageServerHandle> {
256 let servers = self.servers.lock().await;
257 servers.get(language).map(|_s| LanguageServerHandle {
258 language: language.to_string(),
259 servers: self.servers.clone(),
260 })
261 }
262
263 pub async fn stop_server(&self, language: &str) -> LspResult<()> {
265 let mut servers = self.servers.lock().await;
266 if let Some(mut server) = servers.remove(language) {
267 server.stop()?;
268 }
269 Ok(())
270 }
271
272 pub async fn stop_all(&self) -> LspResult<()> {
274 let mut servers = self.servers.lock().await;
275 for (_, mut server) in servers.drain() {
276 let _ = server.stop();
277 }
278 Ok(())
279 }
280
281 pub async fn is_running(&self, language: &str) -> bool {
283 let servers = self.servers.lock().await;
284 servers
285 .get(language)
286 .map(|s| s.is_running())
287 .unwrap_or(false)
288 }
289
290 pub fn add_custom_config(&mut self, language: String, config: LanguageServerConfig) {
292 self.configs.insert(language, config);
293 }
294}
295
296impl Default for LanguageServerManager {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302pub struct LanguageServerHandle {
304 language: String,
305 servers: Arc<Mutex<HashMap<String, LanguageServer>>>,
306}
307
308impl LanguageServerHandle {
309 pub fn name(&self) -> String {
311 self.language.clone()
312 }
313
314 pub async fn config(&self) -> Option<LanguageServerConfig> {
316 let servers = self.servers.lock().await;
317 servers.get(&self.language).map(|s| s.config().clone())
318 }
319
320 pub async fn root_path(&self) -> Option<PathBuf> {
322 let servers = self.servers.lock().await;
323 servers.get(&self.language).map(|s| s.root_path().clone())
324 }
325}
326
327pub fn rust_analyzer_config() -> LanguageServerConfig {
333 LanguageServerConfig::new(
334 "rust-analyzer",
335 "rust-analyzer",
336 vec!["--stdio".to_string()],
337 vec!["rs".to_string()],
338 )
339}
340
341pub fn pyright_config() -> LanguageServerConfig {
343 LanguageServerConfig::new(
344 "pyright",
345 "pyright-langserver",
346 vec!["--stdio".to_string()],
347 vec!["py".to_string()],
348 )
349}
350
351pub fn typescript_config() -> LanguageServerConfig {
353 LanguageServerConfig::new(
354 "typescript-language-server",
355 "typescript-language-server",
356 vec!["--stdio".to_string()],
357 vec![
358 "ts".to_string(),
359 "tsx".to_string(),
360 "js".to_string(),
361 "jsx".to_string(),
362 ],
363 )
364}
365
366pub fn gopls_config() -> LanguageServerConfig {
368 LanguageServerConfig::new(
369 "gopls",
370 "gopls",
371 vec!["serve".to_string(), "--stdio".to_string()],
372 vec!["go".to_string()],
373 )
374}
375
376pub fn clangd_config() -> LanguageServerConfig {
378 LanguageServerConfig::new(
379 "clangd",
380 "clangd",
381 vec![
382 "--background-index".to_string(),
383 "--header-insertion=iwyu".to_string(),
384 ],
385 vec![
386 "c".to_string(),
387 "cpp".to_string(),
388 "h".to_string(),
389 "hpp".to_string(),
390 ],
391 )
392}
393
394pub fn pylance_config() -> LanguageServerConfig {
396 LanguageServerConfig::new(
397 "pylance",
398 "pylance",
399 vec!["--stdio".to_string()],
400 vec!["py".to_string()],
401 )
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_manager_creation() {
410 let manager = LanguageServerManager::new();
411 assert!(manager.get_config("rust").is_some());
412 assert!(manager.get_config("python").is_some());
413 assert!(manager.get_config("typescript").is_some());
414 assert!(manager.get_config("go").is_some());
415 assert!(manager.get_config("cpp").is_some());
416 }
417
418 #[test]
419 fn test_get_language_from_extension() {
420 assert_eq!(
421 LanguageServerManager::get_language_from_extension("rs"),
422 Some("rust")
423 );
424 assert_eq!(
425 LanguageServerManager::get_language_from_extension("py"),
426 Some("python")
427 );
428 assert_eq!(
429 LanguageServerManager::get_language_from_extension("ts"),
430 Some("typescript")
431 );
432 assert_eq!(
433 LanguageServerManager::get_language_from_extension("go"),
434 Some("go")
435 );
436 assert_eq!(
437 LanguageServerManager::get_language_from_extension("cpp"),
438 Some("cpp")
439 );
440 assert_eq!(
441 LanguageServerManager::get_language_from_extension("xyz"),
442 None
443 );
444 }
445
446 #[test]
447 fn test_config_creation() {
448 let config = rust_analyzer_config();
449 assert_eq!(config.name, "rust-analyzer");
450 assert_eq!(config.command, "rust-analyzer");
451 assert_eq!(config.extensions, vec!["rs".to_string()]);
452 }
453}