Skip to main content

edb_tui/
config.rs

1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Configuration system for EDB TUI
18//!
19//! Manages user preferences including color schemes and other settings.
20
21use 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/// Main configuration structure
31#[derive(Debug, Clone, Serialize, Deserialize, Default)]
32pub struct Config {
33    /// Current theme configuration
34    pub theme: ThemeConfig,
35    /// Panel-specific settings
36    pub panels: PanelConfig,
37}
38
39/// Theme configuration
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ThemeConfig {
42    /// Current active theme name
43    pub active: String,
44    /// Available themes
45    pub themes: std::collections::HashMap<String, Theme>,
46}
47
48/// Panel-specific configuration
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PanelConfig {
51    /// Terminal panel settings
52    pub terminal: TerminalPanelConfig,
53    /// Code panel settings
54    pub code: CodePanelConfig,
55    /// Trace panel settings
56    pub trace: TracePanelConfig,
57    /// Display panel settings
58    pub display: DisplayPanelConfig,
59}
60
61/// Terminal panel configuration
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct TerminalPanelConfig {
64    /// Maximum number of history lines to keep
65    pub max_history: usize,
66    /// Show timestamps in output
67    pub show_timestamps: bool,
68}
69
70/// Code panel configuration
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct CodePanelConfig {
73    /// Show line numbers
74    pub show_line_numbers: bool,
75    /// Highlight current line
76    pub highlight_current_line: bool,
77}
78
79/// Trace panel configuration
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct TracePanelConfig {
82    /// Show trace depth indicators
83    pub show_depth_indicators: bool,
84    /// Maximum trace entries to display
85    pub max_entries: usize,
86}
87
88/// Display panel configuration
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct DisplayPanelConfig {
91    /// Default display mode on startup
92    pub default_mode: String,
93    /// Show variable types
94    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    /// Get the config file path (~/.edb.toml)
118    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    /// Load configuration from file, creating default if it doesn't exist
125    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    /// Load configuration from a specific path
146    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    /// Save configuration to file
162    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    /// Get the currently active theme
176    pub fn get_active_theme(&self) -> Option<&Theme> {
177        self.theme.themes.get(&self.theme.active)
178    }
179
180    /// Switch to a different theme
181    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    /// List available themes
192    pub fn list_themes(&self) -> Vec<(&String, &Theme)> {
193        self.theme.themes.iter().collect()
194    }
195
196    /// Convert color string to ratatui Color
197    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}