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