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/// TCP adapter spawn style
46#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
47pub enum TcpSpawnStyle {
48    /// Adapter accepts --listen flag and waits for connection (Delve)
49    #[default]
50    #[serde(rename = "tcp-listen")]
51    TcpListen,
52    /// Adapter receives port as positional argument (js-debug)
53    #[serde(rename = "tcp-port-arg")]
54    TcpPortArg,
55}
56
57/// Configuration for a debug adapter
58#[derive(Debug, Deserialize, Clone)]
59pub struct AdapterConfig {
60    /// Path to the adapter executable
61    pub path: PathBuf,
62
63    /// Additional arguments to pass to the adapter
64    #[serde(default)]
65    pub args: Vec<String>,
66
67    /// Transport mode for DAP communication
68    #[serde(default)]
69    pub transport: TransportMode,
70
71    /// TCP spawn style (only used when transport is Tcp)
72    #[serde(default)]
73    pub spawn_style: TcpSpawnStyle,
74}
75
76/// Default settings
77#[derive(Debug, Deserialize)]
78pub struct Defaults {
79    /// Default adapter to use
80    #[serde(default = "default_adapter")]
81    pub adapter: String,
82}
83
84impl Default for Defaults {
85    fn default() -> Self {
86        Self {
87            adapter: default_adapter(),
88        }
89    }
90}
91
92fn default_adapter() -> String {
93    "lldb-dap".to_string()
94}
95
96/// Timeout settings in seconds
97#[derive(Debug, Deserialize)]
98pub struct Timeouts {
99    /// Timeout for DAP initialize request
100    #[serde(default = "default_dap_initialize")]
101    pub dap_initialize_secs: u64,
102
103    /// Timeout for general DAP requests
104    #[serde(default = "default_dap_request")]
105    pub dap_request_secs: u64,
106
107    /// Default timeout for await command
108    #[serde(default = "default_await")]
109    pub await_default_secs: u64,
110}
111
112impl Default for Timeouts {
113    fn default() -> Self {
114        Self {
115            dap_initialize_secs: default_dap_initialize(),
116            dap_request_secs: default_dap_request(),
117            await_default_secs: default_await(),
118        }
119    }
120}
121
122fn default_dap_initialize() -> u64 {
123    10
124}
125fn default_dap_request() -> u64 {
126    30
127}
128fn default_await() -> u64 {
129    300
130}
131
132/// Daemon configuration
133#[derive(Debug, Deserialize)]
134pub struct DaemonConfig {
135    /// Auto-exit after this many minutes with no active session
136    #[serde(default = "default_idle_timeout")]
137    pub idle_timeout_minutes: u64,
138}
139
140impl Default for DaemonConfig {
141    fn default() -> Self {
142        Self {
143            idle_timeout_minutes: default_idle_timeout(),
144        }
145    }
146}
147
148fn default_idle_timeout() -> u64 {
149    30
150}
151
152/// Output buffer configuration
153#[derive(Debug, Deserialize)]
154pub struct OutputConfig {
155    /// Maximum number of output events to buffer
156    #[serde(default = "default_max_events")]
157    pub max_events: usize,
158
159    /// Maximum total bytes to buffer
160    #[serde(default = "default_max_bytes")]
161    pub max_bytes_mb: usize,
162}
163
164impl Default for OutputConfig {
165    fn default() -> Self {
166        Self {
167            max_events: default_max_events(),
168            max_bytes_mb: default_max_bytes(),
169        }
170    }
171}
172
173fn default_max_events() -> usize {
174    10_000
175}
176fn default_max_bytes() -> usize {
177    10
178}
179
180impl Config {
181    /// Load configuration from the default config file
182    ///
183    /// Returns default configuration if file doesn't exist
184    pub fn load() -> Result<Self> {
185        if let Some(path) = config_path() {
186            if path.exists() {
187                let content = std::fs::read_to_string(&path).map_err(|e| {
188                    super::Error::FileRead {
189                        path: path.display().to_string(),
190                        error: e.to_string(),
191                    }
192                })?;
193                return toml::from_str(&content)
194                    .map_err(|e| super::Error::ConfigParse(e.to_string()));
195            }
196        }
197        Ok(Self::default())
198    }
199
200    /// Get adapter configuration by name
201    ///
202    /// Falls back to searching PATH if not explicitly configured
203    pub fn get_adapter(&self, name: &str) -> Option<AdapterConfig> {
204        // Check explicit configuration first
205        if let Some(config) = self.adapters.get(name) {
206            return Some(config.clone());
207        }
208
209        // Try to find in PATH
210        which::which(name).ok().map(|path| AdapterConfig {
211            path,
212            args: Vec::new(),
213            transport: TransportMode::default(),
214            spawn_style: TcpSpawnStyle::default(),
215        })
216    }
217}