Skip to main content

debugger/common/
config.rs

1//! Configuration file handling
2
3use serde::Deserialize;
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7use super::paths::config_path;
8use super::Result;
9
10/// Main configuration structure
11#[derive(Debug, Deserialize, Default)]
12pub struct Config {
13    /// Debug adapter configurations
14    #[serde(default)]
15    pub adapters: HashMap<String, AdapterConfig>,
16
17    /// Default settings
18    #[serde(default)]
19    pub defaults: Defaults,
20
21    /// Timeout settings
22    #[serde(default)]
23    pub timeouts: Timeouts,
24
25    /// Daemon settings
26    #[serde(default)]
27    pub daemon: DaemonConfig,
28
29    /// Output buffer settings
30    #[serde(default)]
31    pub output: OutputConfig,
32}
33
34/// Transport mode for debug adapter communication
35#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
36#[serde(rename_all = "lowercase")]
37pub enum TransportMode {
38    /// Standard input/output (default for most adapters)
39    #[default]
40    Stdio,
41    /// TCP socket connection (used by Delve)
42    Tcp,
43}
44
45/// Configuration for a debug adapter
46#[derive(Debug, Deserialize, Clone)]
47pub struct AdapterConfig {
48    /// Path to the adapter executable
49    pub path: PathBuf,
50
51    /// Additional arguments to pass to the adapter
52    #[serde(default)]
53    pub args: Vec<String>,
54
55    /// Transport mode for DAP communication
56    #[serde(default)]
57    pub transport: TransportMode,
58}
59
60/// Default settings
61#[derive(Debug, Deserialize)]
62pub struct Defaults {
63    /// Default adapter to use
64    #[serde(default = "default_adapter")]
65    pub adapter: String,
66}
67
68impl Default for Defaults {
69    fn default() -> Self {
70        Self {
71            adapter: default_adapter(),
72        }
73    }
74}
75
76fn default_adapter() -> String {
77    "lldb-dap".to_string()
78}
79
80/// Timeout settings in seconds
81#[derive(Debug, Deserialize)]
82pub struct Timeouts {
83    /// Timeout for DAP initialize request
84    #[serde(default = "default_dap_initialize")]
85    pub dap_initialize_secs: u64,
86
87    /// Timeout for general DAP requests
88    #[serde(default = "default_dap_request")]
89    pub dap_request_secs: u64,
90
91    /// Default timeout for await command
92    #[serde(default = "default_await")]
93    pub await_default_secs: u64,
94}
95
96impl Default for Timeouts {
97    fn default() -> Self {
98        Self {
99            dap_initialize_secs: default_dap_initialize(),
100            dap_request_secs: default_dap_request(),
101            await_default_secs: default_await(),
102        }
103    }
104}
105
106fn default_dap_initialize() -> u64 {
107    10
108}
109fn default_dap_request() -> u64 {
110    30
111}
112fn default_await() -> u64 {
113    300
114}
115
116/// Daemon configuration
117#[derive(Debug, Deserialize)]
118pub struct DaemonConfig {
119    /// Auto-exit after this many minutes with no active session
120    #[serde(default = "default_idle_timeout")]
121    pub idle_timeout_minutes: u64,
122}
123
124impl Default for DaemonConfig {
125    fn default() -> Self {
126        Self {
127            idle_timeout_minutes: default_idle_timeout(),
128        }
129    }
130}
131
132fn default_idle_timeout() -> u64 {
133    30
134}
135
136/// Output buffer configuration
137#[derive(Debug, Deserialize)]
138pub struct OutputConfig {
139    /// Maximum number of output events to buffer
140    #[serde(default = "default_max_events")]
141    pub max_events: usize,
142
143    /// Maximum total bytes to buffer
144    #[serde(default = "default_max_bytes")]
145    pub max_bytes_mb: usize,
146}
147
148impl Default for OutputConfig {
149    fn default() -> Self {
150        Self {
151            max_events: default_max_events(),
152            max_bytes_mb: default_max_bytes(),
153        }
154    }
155}
156
157fn default_max_events() -> usize {
158    10_000
159}
160fn default_max_bytes() -> usize {
161    10
162}
163
164impl Config {
165    /// Load configuration from the default config file
166    ///
167    /// Returns default configuration if file doesn't exist
168    pub fn load() -> Result<Self> {
169        if let Some(path) = config_path() {
170            if path.exists() {
171                let content = std::fs::read_to_string(&path).map_err(|e| {
172                    super::Error::FileRead {
173                        path: path.display().to_string(),
174                        error: e.to_string(),
175                    }
176                })?;
177                return toml::from_str(&content)
178                    .map_err(|e| super::Error::ConfigParse(e.to_string()));
179            }
180        }
181        Ok(Self::default())
182    }
183
184    /// Get adapter configuration by name
185    ///
186    /// Falls back to searching PATH if not explicitly configured
187    pub fn get_adapter(&self, name: &str) -> Option<AdapterConfig> {
188        // Check explicit configuration first
189        if let Some(config) = self.adapters.get(name) {
190            return Some(config.clone());
191        }
192
193        // Try to find in PATH
194        which::which(name).ok().map(|path| AdapterConfig {
195            path,
196            args: Vec::new(),
197            transport: TransportMode::default(),
198        })
199    }
200}