1use crate::error::{LspError, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::time::Duration;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct LspServerConfig {
12 pub command: String,
14
15 pub args: Vec<String>,
17
18 pub working_directory: Option<PathBuf>,
20
21 pub environment: HashMap<String, String>,
23
24 pub root_path: Option<PathBuf>,
26
27 pub workspace_folders: Vec<PathBuf>,
29
30 pub initialization_options: Option<serde_json::Value>,
32
33 pub client_capabilities: LspClientCapabilities,
35
36 pub startup_timeout: Duration,
38
39 pub request_timeout: Duration,
41
42 pub trace: TraceLevel,
44
45 pub settings: HashMap<String, serde_json::Value>,
47
48 pub max_restart_attempts: u32,
50
51 pub restart_delay: Duration,
53}
54
55impl Default for LspServerConfig {
56 fn default() -> Self {
57 Self {
58 command: String::new(),
59 args: Vec::new(),
60 working_directory: None,
61 environment: HashMap::new(),
62 root_path: None,
63 workspace_folders: Vec::new(),
64 initialization_options: None,
65 client_capabilities: LspClientCapabilities::default(),
66 startup_timeout: Duration::from_secs(30),
67 request_timeout: Duration::from_secs(30),
68 trace: TraceLevel::Off,
69 settings: HashMap::new(),
70 max_restart_attempts: 3,
71 restart_delay: Duration::from_secs(2),
72 }
73 }
74}
75
76impl LspServerConfig {
77 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn command<S: Into<String>>(mut self, command: S) -> Self {
84 self.command = command.into();
85 self
86 }
87
88 pub fn args<I, S>(mut self, args: I) -> Self
90 where
91 I: IntoIterator<Item = S>,
92 S: Into<String>,
93 {
94 self.args.extend(args.into_iter().map(|s| s.into()));
95 self
96 }
97
98 pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
100 self.args.push(arg.into());
101 self
102 }
103
104 pub fn working_directory<P: AsRef<Path>>(mut self, path: P) -> Self {
106 self.working_directory = Some(path.as_ref().to_path_buf());
107 self
108 }
109
110 pub fn env<K, V>(mut self, key: K, value: V) -> Self
112 where
113 K: Into<String>,
114 V: Into<String>,
115 {
116 self.environment.insert(key.into(), value.into());
117 self
118 }
119
120 pub fn envs<I, K, V>(mut self, vars: I) -> Self
122 where
123 I: IntoIterator<Item = (K, V)>,
124 K: Into<String>,
125 V: Into<String>,
126 {
127 for (key, value) in vars {
128 self.environment.insert(key.into(), value.into());
129 }
130 self
131 }
132
133 pub fn root_path<P: AsRef<Path>>(mut self, path: P) -> Self {
135 self.root_path = Some(path.as_ref().to_path_buf());
136 self
137 }
138
139 pub fn workspace_folders<I, P>(mut self, folders: I) -> Self
141 where
142 I: IntoIterator<Item = P>,
143 P: AsRef<Path>,
144 {
145 self.workspace_folders
146 .extend(folders.into_iter().map(|p| p.as_ref().to_path_buf()));
147 self
148 }
149
150 pub fn workspace_folder<P: AsRef<Path>>(mut self, folder: P) -> Self {
152 self.workspace_folders.push(folder.as_ref().to_path_buf());
153 self
154 }
155
156 pub fn initialization_options(mut self, options: serde_json::Value) -> Self {
158 self.initialization_options = Some(options);
159 self
160 }
161
162 pub fn client_capabilities(mut self, capabilities: LspClientCapabilities) -> Self {
164 self.client_capabilities = capabilities;
165 self
166 }
167
168 pub fn startup_timeout(mut self, timeout: Duration) -> Self {
170 self.startup_timeout = timeout;
171 self
172 }
173
174 pub fn request_timeout(mut self, timeout: Duration) -> Self {
176 self.request_timeout = timeout;
177 self
178 }
179
180 pub fn trace(mut self, level: TraceLevel) -> Self {
182 self.trace = level;
183 self
184 }
185
186 pub fn setting<K, V>(mut self, key: K, value: V) -> Self
188 where
189 K: Into<String>,
190 V: Serialize,
191 {
192 if let Ok(json_value) = serde_json::to_value(value) {
193 self.settings.insert(key.into(), json_value);
194 }
195 self
196 }
197
198 pub fn max_restart_attempts(mut self, attempts: u32) -> Self {
200 self.max_restart_attempts = attempts;
201 self
202 }
203
204 pub fn restart_delay(mut self, delay: Duration) -> Self {
206 self.restart_delay = delay;
207 self
208 }
209
210 pub fn validate(&self) -> Result<()> {
212 if self.command.is_empty() {
213 return Err(LspError::invalid_configuration("Command cannot be empty").into());
214 }
215
216 if self.startup_timeout.is_zero() {
217 return Err(LspError::invalid_configuration(
218 "Startup timeout must be greater than zero",
219 )
220 .into());
221 }
222
223 if self.request_timeout.is_zero() {
224 return Err(LspError::invalid_configuration(
225 "Request timeout must be greater than zero",
226 )
227 .into());
228 }
229
230 Ok(())
231 }
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize, Default)]
236pub struct LspClientCapabilities {
237 pub text_document: TextDocumentClientCapabilities,
239
240 pub workspace: WorkspaceClientCapabilities,
242
243 pub window: WindowClientCapabilities,
245
246 pub general: GeneralClientCapabilities,
248
249 pub experimental: Option<serde_json::Value>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, Default)]
255pub struct TextDocumentClientCapabilities {
256 pub synchronization: Option<TextDocumentSyncClientCapabilities>,
257 pub completion: Option<CompletionClientCapabilities>,
258 pub hover: Option<HoverClientCapabilities>,
259 pub signature_help: Option<SignatureHelpClientCapabilities>,
260 pub declaration: Option<GotoCapability>,
261 pub definition: Option<GotoCapability>,
262 pub type_definition: Option<GotoCapability>,
263 pub implementation: Option<GotoCapability>,
264 pub references: Option<ReferenceClientCapabilities>,
265 pub document_highlight: Option<DocumentHighlightClientCapabilities>,
266 pub document_symbol: Option<DocumentSymbolClientCapabilities>,
267 pub code_action: Option<CodeActionClientCapabilities>,
268 pub code_lens: Option<CodeLensClientCapabilities>,
269 pub document_link: Option<DocumentLinkClientCapabilities>,
270 pub color_provider: Option<DocumentColorClientCapabilities>,
271 pub formatting: Option<DocumentFormattingClientCapabilities>,
272 pub range_formatting: Option<DocumentRangeFormattingClientCapabilities>,
273 pub on_type_formatting: Option<DocumentOnTypeFormattingClientCapabilities>,
274 pub rename: Option<RenameClientCapabilities>,
275 pub folding_range: Option<FoldingRangeClientCapabilities>,
276 pub selection_range: Option<SelectionRangeClientCapabilities>,
277 pub publish_diagnostics: Option<PublishDiagnosticsClientCapabilities>,
278 pub call_hierarchy: Option<CallHierarchyClientCapabilities>,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize, Default)]
283pub struct WorkspaceClientCapabilities {
284 pub apply_edit: Option<bool>,
285 pub workspace_edit: Option<WorkspaceEditClientCapabilities>,
286 pub did_change_configuration: Option<DidChangeConfigurationClientCapabilities>,
287 pub did_change_watched_files: Option<DidChangeWatchedFilesClientCapabilities>,
288 pub symbol: Option<WorkspaceSymbolClientCapabilities>,
289 pub execute_command: Option<ExecuteCommandClientCapabilities>,
290 pub workspace_folders: Option<bool>,
291 pub configuration: Option<bool>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, Default)]
296pub struct WindowClientCapabilities {
297 pub work_done_progress: Option<bool>,
298 pub show_message: Option<ShowMessageRequestClientCapabilities>,
299 pub show_document: Option<ShowDocumentClientCapabilities>,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, Default)]
304pub struct GeneralClientCapabilities {
305 pub regular_expressions: Option<RegularExpressionsClientCapabilities>,
306 pub markdown: Option<MarkdownClientCapabilities>,
307}
308
309#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
311pub enum TraceLevel {
312 #[serde(rename = "off")]
313 Off,
314 #[serde(rename = "messages")]
315 Messages,
316 #[serde(rename = "verbose")]
317 Verbose,
318}
319
320impl Default for TraceLevel {
321 fn default() -> Self {
322 Self::Off
323 }
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize, Default)]
330pub struct TextDocumentSyncClientCapabilities;
331
332#[derive(Debug, Clone, Serialize, Deserialize, Default)]
333pub struct CompletionClientCapabilities;
334
335#[derive(Debug, Clone, Serialize, Deserialize, Default)]
336pub struct HoverClientCapabilities;
337
338#[derive(Debug, Clone, Serialize, Deserialize, Default)]
339pub struct SignatureHelpClientCapabilities;
340
341#[derive(Debug, Clone, Serialize, Deserialize, Default)]
342pub struct GotoCapability;
343
344#[derive(Debug, Clone, Serialize, Deserialize, Default)]
345pub struct ReferenceClientCapabilities;
346
347#[derive(Debug, Clone, Serialize, Deserialize, Default)]
348pub struct DocumentHighlightClientCapabilities;
349
350#[derive(Debug, Clone, Serialize, Deserialize, Default)]
351pub struct DocumentSymbolClientCapabilities;
352
353#[derive(Debug, Clone, Serialize, Deserialize, Default)]
354pub struct CodeActionClientCapabilities;
355
356#[derive(Debug, Clone, Serialize, Deserialize, Default)]
357pub struct CodeLensClientCapabilities;
358
359#[derive(Debug, Clone, Serialize, Deserialize, Default)]
360pub struct DocumentLinkClientCapabilities;
361
362#[derive(Debug, Clone, Serialize, Deserialize, Default)]
363pub struct DocumentColorClientCapabilities;
364
365#[derive(Debug, Clone, Serialize, Deserialize, Default)]
366pub struct DocumentFormattingClientCapabilities;
367
368#[derive(Debug, Clone, Serialize, Deserialize, Default)]
369pub struct DocumentRangeFormattingClientCapabilities;
370
371#[derive(Debug, Clone, Serialize, Deserialize, Default)]
372pub struct DocumentOnTypeFormattingClientCapabilities;
373
374#[derive(Debug, Clone, Serialize, Deserialize, Default)]
375pub struct RenameClientCapabilities;
376
377#[derive(Debug, Clone, Serialize, Deserialize, Default)]
378pub struct FoldingRangeClientCapabilities;
379
380#[derive(Debug, Clone, Serialize, Deserialize, Default)]
381pub struct SelectionRangeClientCapabilities;
382
383#[derive(Debug, Clone, Serialize, Deserialize, Default)]
384pub struct PublishDiagnosticsClientCapabilities;
385
386#[derive(Debug, Clone, Serialize, Deserialize, Default)]
387pub struct CallHierarchyClientCapabilities;
388
389#[derive(Debug, Clone, Serialize, Deserialize, Default)]
390pub struct WorkspaceEditClientCapabilities;
391
392#[derive(Debug, Clone, Serialize, Deserialize, Default)]
393pub struct DidChangeConfigurationClientCapabilities;
394
395#[derive(Debug, Clone, Serialize, Deserialize, Default)]
396pub struct DidChangeWatchedFilesClientCapabilities;
397
398#[derive(Debug, Clone, Serialize, Deserialize, Default)]
399pub struct WorkspaceSymbolClientCapabilities;
400
401#[derive(Debug, Clone, Serialize, Deserialize, Default)]
402pub struct ExecuteCommandClientCapabilities;
403
404#[derive(Debug, Clone, Serialize, Deserialize, Default)]
405pub struct ShowMessageRequestClientCapabilities;
406
407#[derive(Debug, Clone, Serialize, Deserialize, Default)]
408pub struct ShowDocumentClientCapabilities;
409
410#[derive(Debug, Clone, Serialize, Deserialize, Default)]
411pub struct RegularExpressionsClientCapabilities;
412
413#[derive(Debug, Clone, Serialize, Deserialize, Default)]
414pub struct MarkdownClientCapabilities;
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_server_config_builder() {
422 let config = LspServerConfig::new()
423 .command("rust-analyzer")
424 .arg("--help")
425 .root_path("/tmp/project")
426 .workspace_folder("/tmp/project/src")
427 .startup_timeout(Duration::from_secs(60));
428
429 assert_eq!(config.command, "rust-analyzer");
430 assert_eq!(config.args, vec!["--help"]);
431 assert_eq!(config.startup_timeout, Duration::from_secs(60));
432 }
433
434 #[test]
435 fn test_config_validation() {
436 let valid_config = LspServerConfig::new().command("test-server");
437 assert!(valid_config.validate().is_ok());
438
439 let invalid_config = LspServerConfig::new(); assert!(invalid_config.validate().is_err());
441 }
442
443 #[test]
444 fn test_client_capabilities_default() {
445 let capabilities = LspClientCapabilities::default();
446 assert!(capabilities.experimental.is_none());
447 }
448}