1use eyre::{Context, Result};
22use ratatui::style::Color;
23use serde::{Deserialize, Serialize};
24use std::fs;
25use std::path::PathBuf;
26use tracing::{debug, info, warn};
27
28use crate::Theme;
29
30#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32pub struct Config {
33 pub theme: ThemeConfig,
35 pub panels: PanelConfig,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ThemeConfig {
42 pub active: String,
44 pub themes: std::collections::HashMap<String, Theme>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PanelConfig {
51 pub terminal: TerminalPanelConfig,
53 pub code: CodePanelConfig,
55 pub trace: TracePanelConfig,
57 pub display: DisplayPanelConfig,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct TerminalPanelConfig {
64 pub max_history: usize,
66 pub show_timestamps: bool,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct CodePanelConfig {
73 pub show_line_numbers: bool,
75 pub highlight_current_line: bool,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct TracePanelConfig {
82 pub show_depth_indicators: bool,
84 pub max_entries: usize,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct DisplayPanelConfig {
91 pub default_mode: String,
93 pub show_types: bool,
95}
96
97impl Default for ThemeConfig {
98 fn default() -> Self {
99 let themes = Theme::all().iter().map(|theme| (theme.name().to_string(), *theme)).collect();
100
101 Self { active: Theme::default().name().to_string(), themes }
102 }
103}
104
105impl Default for PanelConfig {
106 fn default() -> Self {
107 Self {
108 terminal: TerminalPanelConfig { max_history: 1000, show_timestamps: false },
109 code: CodePanelConfig { show_line_numbers: true, highlight_current_line: true },
110 trace: TracePanelConfig { show_depth_indicators: true, max_entries: 500 },
111 display: DisplayPanelConfig { default_mode: "Variables".to_string(), show_types: true },
112 }
113 }
114}
115
116impl Config {
117 pub fn config_path() -> Result<PathBuf> {
119 let home =
120 dirs::home_dir().ok_or_else(|| eyre::eyre!("Unable to determine home directory"))?;
121 Ok(home.join(".edb.toml"))
122 }
123
124 pub fn load() -> Result<Self> {
126 let config_path = Self::config_path()?;
127
128 if !config_path.exists() {
129 info!("Config file not found, creating default at {:?}", config_path);
130 let default_config = Self::default();
131 default_config.save()?;
132 return Ok(default_config);
133 }
134
135 let content = fs::read_to_string(&config_path)
136 .with_context(|| format!("Failed to read config file: {config_path:?}"))?;
137
138 let config: Self =
139 toml::from_str(&content).with_context(|| "Failed to parse config file as TOML")?;
140
141 debug!("Loaded configuration from {:?}", config_path);
142 Ok(config)
143 }
144
145 pub fn load_from_path(path: PathBuf) -> Result<Self> {
147 if !path.exists() {
148 return Err(eyre::eyre!("Config file not found at {:?}", path));
149 }
150
151 let content = fs::read_to_string(&path)
152 .with_context(|| format!("Failed to read config file: {path:?}"))?;
153
154 let config: Self =
155 toml::from_str(&content).with_context(|| "Failed to parse config file as TOML")?;
156
157 debug!("Loaded configuration from {:?}", path);
158 Ok(config)
159 }
160
161 pub fn save(&self) -> Result<()> {
163 let config_path = Self::config_path()?;
164
165 let content =
166 toml::to_string_pretty(self).with_context(|| "Failed to serialize config to TOML")?;
167
168 fs::write(&config_path, content)
169 .with_context(|| format!("Failed to write config file: {config_path:?}"))?;
170
171 debug!("Saved configuration to {:?}", config_path);
172 Ok(())
173 }
174
175 pub fn get_active_theme(&self) -> Option<&Theme> {
177 self.theme.themes.get(&self.theme.active)
178 }
179
180 pub fn set_theme(&mut self, theme_name: &str) -> Result<()> {
182 if !self.theme.themes.contains_key(theme_name) {
183 return Err(eyre::eyre!("Theme '{}' not found", theme_name));
184 }
185
186 self.theme.active = theme_name.to_string();
187 info!("Switched to theme: {}", theme_name);
188 Ok(())
189 }
190
191 pub fn list_themes(&self) -> Vec<(&String, &Theme)> {
193 self.theme.themes.iter().collect()
194 }
195
196 pub fn parse_color(color_str: &str) -> Color {
198 match color_str.to_lowercase().as_str() {
199 "black" => Color::Black,
200 "red" => Color::Red,
201 "green" => Color::Green,
202 "yellow" => Color::Yellow,
203 "blue" => Color::Blue,
204 "magenta" => Color::Magenta,
205 "cyan" => Color::Cyan,
206 "gray" => Color::Gray,
207 "dark_gray" => Color::DarkGray,
208 "light_red" => Color::LightRed,
209 "light_green" => Color::LightGreen,
210 "light_yellow" => Color::LightYellow,
211 "light_blue" => Color::LightBlue,
212 "light_magenta" => Color::LightMagenta,
213 "light_cyan" => Color::LightCyan,
214 "white" => Color::White,
215 "light_gray" => Color::Gray,
216 _ => {
217 warn!("Unknown color '{}', using default gray", color_str);
218 Color::Gray
219 }
220 }
221 }
222}