lsp_bridge/
config.rs

1//! Configuration types and builders for LSP Bridge.
2
3use crate::error::{LspError, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7use std::time::Duration;
8
9/// Configuration for an LSP server instance.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct LspServerConfig {
12    /// Command to execute the LSP server
13    pub command: String,
14
15    /// Arguments to pass to the server command
16    pub args: Vec<String>,
17
18    /// Working directory for the server process
19    pub working_directory: Option<PathBuf>,
20
21    /// Environment variables for the server process
22    pub environment: HashMap<String, String>,
23
24    /// Root path/URI for the workspace
25    pub root_path: Option<PathBuf>,
26
27    /// Workspace folders
28    pub workspace_folders: Vec<PathBuf>,
29
30    /// Initialization options to send to the server
31    pub initialization_options: Option<serde_json::Value>,
32
33    /// Client capabilities to advertise
34    pub client_capabilities: LspClientCapabilities,
35
36    /// Server startup timeout
37    pub startup_timeout: Duration,
38
39    /// Request timeout
40    pub request_timeout: Duration,
41
42    /// Whether to enable tracing
43    pub trace: TraceLevel,
44
45    /// Custom server settings
46    pub settings: HashMap<String, serde_json::Value>,
47
48    /// Maximum number of restart attempts
49    pub max_restart_attempts: u32,
50
51    /// Restart delay between attempts
52    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    /// Create a new server configuration
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    /// Set the server command
83    pub fn command<S: Into<String>>(mut self, command: S) -> Self {
84        self.command = command.into();
85        self
86    }
87
88    /// Add arguments to the server command
89    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    /// Add a single argument to the server command
99    pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
100        self.args.push(arg.into());
101        self
102    }
103
104    /// Set the working directory
105    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    /// Add an environment variable
111    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    /// Set multiple environment variables
121    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    /// Set the root path
134    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    /// Add workspace folders
140    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    /// Add a single workspace folder
151    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    /// Set initialization options
157    pub fn initialization_options(mut self, options: serde_json::Value) -> Self {
158        self.initialization_options = Some(options);
159        self
160    }
161
162    /// Set client capabilities
163    pub fn client_capabilities(mut self, capabilities: LspClientCapabilities) -> Self {
164        self.client_capabilities = capabilities;
165        self
166    }
167
168    /// Set startup timeout
169    pub fn startup_timeout(mut self, timeout: Duration) -> Self {
170        self.startup_timeout = timeout;
171        self
172    }
173
174    /// Set request timeout
175    pub fn request_timeout(mut self, timeout: Duration) -> Self {
176        self.request_timeout = timeout;
177        self
178    }
179
180    /// Set trace level
181    pub fn trace(mut self, level: TraceLevel) -> Self {
182        self.trace = level;
183        self
184    }
185
186    /// Add a server setting
187    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    /// Set maximum restart attempts
199    pub fn max_restart_attempts(mut self, attempts: u32) -> Self {
200        self.max_restart_attempts = attempts;
201        self
202    }
203
204    /// Set restart delay
205    pub fn restart_delay(mut self, delay: Duration) -> Self {
206        self.restart_delay = delay;
207        self
208    }
209
210    /// Validate the configuration
211    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/// Client capabilities configuration.
235#[derive(Debug, Clone, Serialize, Deserialize, Default)]
236pub struct LspClientCapabilities {
237    /// Text document capabilities
238    pub text_document: TextDocumentClientCapabilities,
239
240    /// Workspace capabilities
241    pub workspace: WorkspaceClientCapabilities,
242
243    /// Window capabilities
244    pub window: WindowClientCapabilities,
245
246    /// General capabilities
247    pub general: GeneralClientCapabilities,
248
249    /// Experimental capabilities
250    pub experimental: Option<serde_json::Value>,
251}
252
253/// Text document client capabilities.
254#[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/// Workspace client capabilities.
282#[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/// Window client capabilities.
295#[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/// General client capabilities.
303#[derive(Debug, Clone, Serialize, Deserialize, Default)]
304pub struct GeneralClientCapabilities {
305    pub regular_expressions: Option<RegularExpressionsClientCapabilities>,
306    pub markdown: Option<MarkdownClientCapabilities>,
307}
308
309/// Trace level for LSP communication.
310#[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// Placeholder types for client capabilities - these would normally be imported from lsp-types
327// but we're defining minimal versions here for the example
328
329#[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(); // No command
440        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}