matrixcode_core/lsp/
manager.rs1use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8use super::types::{LspConfig, LspServerConfig, LspServerInfo, LspServerStatus};
9
10pub struct LspManager {
14 configs: HashMap<String, LspServerConfig>,
16 statuses: Arc<RwLock<HashMap<String, LspServerStatus>>>,
18}
19
20impl LspManager {
21 pub fn new() -> Self {
23 Self {
24 configs: HashMap::new(),
25 statuses: Arc::new(RwLock::new(HashMap::new())),
26 }
27 }
28
29 pub fn from_config(config: &LspConfig) -> Self {
31 let configs: HashMap<String, LspServerConfig> = config
32 .servers
33 .iter()
34 .filter(|s| s.enabled)
35 .map(|s| (s.language.clone(), s.clone()))
36 .collect();
37
38 let statuses = configs
40 .keys()
41 .map(|k| (k.clone(), LspServerStatus::NotStarted))
42 .collect();
43
44 Self {
45 configs,
46 statuses: Arc::new(RwLock::new(statuses)),
47 }
48 }
49
50 pub fn add_server(&mut self, config: LspServerConfig) {
52 let name = config.language.clone();
53 self.configs.insert(name.clone(), config);
54 if let Ok(mut statuses) = self.statuses.write() {
55 statuses.insert(name, LspServerStatus::NotStarted);
56 }
57 }
58
59 pub fn remove_server(&mut self, name: &str) {
61 self.configs.remove(name);
62 if let Ok(mut statuses) = self.statuses.write() {
63 statuses.remove(name);
64 }
65 }
66
67 pub fn server_infos(&self) -> Vec<LspServerInfo> {
69 if let Ok(statuses) = self.statuses.read() {
70 self.configs
71 .iter()
72 .map(|(name, config)| {
73 let status = statuses
74 .get(name)
75 .cloned()
76 .unwrap_or(LspServerStatus::NotStarted);
77 LspServerInfo {
78 name: config.command.clone(),
79 language: config.language.clone(),
80 status,
81 }
82 })
83 .collect()
84 } else {
85 self.configs
87 .iter()
88 .map(|(_name, config)| LspServerInfo {
89 name: config.command.clone(),
90 language: config.language.clone(),
91 status: LspServerStatus::NotStarted,
92 })
93 .collect()
94 }
95 }
96
97 pub fn get_status(&self, name: &str) -> LspServerStatus {
99 self.statuses
100 .read()
101 .map(|s| s.get(name).cloned().unwrap_or(LspServerStatus::NotStarted))
102 .unwrap_or(LspServerStatus::NotStarted)
103 }
104
105 pub fn set_status(&self, name: &str, status: LspServerStatus) {
107 if let Ok(mut statuses) = self.statuses.write() {
108 statuses.insert(name.to_string(), status);
109 }
110 }
111
112 pub fn mark_connected(&self, name: &str) {
114 self.set_status(name, LspServerStatus::Connected);
115 }
116
117 pub fn mark_error(&self, name: &str, msg: impl Into<String>) {
119 self.set_status(name, LspServerStatus::Error(msg.into()));
120 }
121
122 pub fn get_config(&self, name: &str) -> Option<&LspServerConfig> {
124 self.configs.get(name)
125 }
126
127 pub fn server_names(&self) -> Vec<&String> {
129 self.configs.keys().collect()
130 }
131
132 pub fn connected_count(&self) -> usize {
134 self.statuses
135 .read()
136 .map(|s| s.values().filter(|s| s.is_ok()).count())
137 .unwrap_or(0)
138 }
139
140 pub fn error_count(&self) -> usize {
142 self.statuses
143 .read()
144 .map(|s| s.values().filter(|s| s.is_error()).count())
145 .unwrap_or(0)
146 }
147
148 pub fn has_servers(&self) -> bool {
150 !self.configs.is_empty()
151 }
152
153 pub fn reset_all(&self) {
155 if let Ok(mut statuses) = self.statuses.write() {
156 for name in self.configs.keys() {
157 statuses.insert(name.clone(), LspServerStatus::NotStarted);
158 }
159 }
160 }
161}
162
163impl Default for LspManager {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169pub fn find_lsp_config(start_dir: &std::path::Path) -> Option<std::path::PathBuf> {
173 let local_config = start_dir.join("lsp.toml");
175 if local_config.exists() {
176 return Some(local_config);
177 }
178
179 if let Some(home) = dirs::home_dir() {
181 let home_config = home.join(".config").join("matrixcode").join("lsp.toml");
182 if home_config.exists() {
183 return Some(home_config);
184 }
185 }
186
187 None
188}
189
190pub fn load_lsp_config(start_dir: &std::path::Path) -> LspConfig {
194 find_lsp_config(start_dir)
195 .and_then(|p| LspConfig::from_file(&p).ok())
196 .unwrap_or_default()
197}