code_mesh_tui/
config.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Configuration for the TUI application
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Config {
7    /// Theme configuration
8    pub theme: ThemeConfig,
9    /// Keybinding configuration
10    pub keybinds: KeybindConfig,
11    /// File viewer configuration
12    pub file_viewer: FileViewerConfig,
13    /// Chat configuration
14    pub chat: ChatConfig,
15    /// Diff viewer configuration
16    pub diff: DiffConfig,
17}
18
19impl Default for Config {
20    fn default() -> Self {
21        Self {
22            theme: ThemeConfig::default(),
23            keybinds: KeybindConfig::default(),
24            file_viewer: FileViewerConfig::default(),
25            chat: ChatConfig::default(),
26            diff: DiffConfig::default(),
27        }
28    }
29}
30
31/// Theme configuration
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ThemeConfig {
34    /// Current theme name
35    pub name: String,
36    /// Custom theme overrides
37    pub custom: Option<CustomTheme>,
38    /// Enable high contrast mode
39    pub high_contrast: bool,
40}
41
42impl Default for ThemeConfig {
43    fn default() -> Self {
44        Self {
45            name: "default".to_string(),
46            custom: None,
47            high_contrast: false,
48        }
49    }
50}
51
52/// Custom theme definition
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct CustomTheme {
55    pub colors: HashMap<String, String>,
56}
57
58/// Keybinding configuration
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct KeybindConfig {
61    /// Leader key for command sequences
62    pub leader: String,
63    /// Custom keybindings
64    pub bindings: HashMap<String, String>,
65}
66
67impl Default for KeybindConfig {
68    fn default() -> Self {
69        let mut bindings = HashMap::new();
70        
71        // Navigation
72        bindings.insert("quit".to_string(), "q".to_string());
73        bindings.insert("help".to_string(), "?".to_string());
74        bindings.insert("command_palette".to_string(), "ctrl+p".to_string());
75        
76        // Chat
77        bindings.insert("send_message".to_string(), "enter".to_string());
78        bindings.insert("new_line".to_string(), "shift+enter".to_string());
79        bindings.insert("clear_input".to_string(), "ctrl+l".to_string());
80        
81        // File viewer
82        bindings.insert("open_file".to_string(), "o".to_string());
83        bindings.insert("close_file".to_string(), "esc".to_string());
84        bindings.insert("toggle_diff".to_string(), "d".to_string());
85        
86        // Navigation
87        bindings.insert("scroll_up".to_string(), "k".to_string());
88        bindings.insert("scroll_down".to_string(), "j".to_string());
89        bindings.insert("page_up".to_string(), "ctrl+u".to_string());
90        bindings.insert("page_down".to_string(), "ctrl+d".to_string());
91        
92        Self {
93            leader: "ctrl+x".to_string(),
94            bindings,
95        }
96    }
97}
98
99/// File viewer configuration
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct FileViewerConfig {
102    /// Maximum file size to display (in bytes)
103    pub max_file_size: usize,
104    /// Enable syntax highlighting
105    pub syntax_highlighting: bool,
106    /// Line number display
107    pub show_line_numbers: bool,
108    /// Word wrapping
109    pub word_wrap: bool,
110}
111
112impl Default for FileViewerConfig {
113    fn default() -> Self {
114        Self {
115            max_file_size: 10 * 1024 * 1024, // 10MB
116            syntax_highlighting: true,
117            show_line_numbers: true,
118            word_wrap: false,
119        }
120    }
121}
122
123/// Chat configuration
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ChatConfig {
126    /// Maximum number of messages to keep in memory
127    pub max_messages: usize,
128    /// Enable message history
129    pub enable_history: bool,
130    /// Auto-scroll to new messages
131    pub auto_scroll: bool,
132    /// Show typing indicators
133    pub show_typing: bool,
134}
135
136impl Default for ChatConfig {
137    fn default() -> Self {
138        Self {
139            max_messages: 1000,
140            enable_history: true,
141            auto_scroll: true,
142            show_typing: true,
143        }
144    }
145}
146
147/// Diff viewer configuration
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct DiffConfig {
150    /// Default diff style (unified or side-by-side)
151    pub default_style: DiffStyle,
152    /// Enable intra-line highlighting
153    pub intra_line_highlighting: bool,
154    /// Context lines for unified diff
155    pub context_lines: usize,
156}
157
158impl Default for DiffConfig {
159    fn default() -> Self {
160        Self {
161            default_style: DiffStyle::SideBySide,
162            intra_line_highlighting: true,
163            context_lines: 3,
164        }
165    }
166}
167
168/// Diff display style
169#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
170pub enum DiffStyle {
171    Unified,
172    SideBySide,
173}
174
175impl Config {
176    /// Load configuration from file
177    pub fn load_from_file(path: &std::path::Path) -> anyhow::Result<Self> {
178        let content = std::fs::read_to_string(path)?;
179        let config: Config = serde_json::from_str(&content)?;
180        Ok(config)
181    }
182
183    /// Save configuration to file
184    pub fn save_to_file(&self, path: &std::path::Path) -> anyhow::Result<()> {
185        let content = serde_json::to_string_pretty(self)?;
186        std::fs::write(path, content)?;
187        Ok(())
188    }
189
190    /// Get the configuration directory
191    pub fn config_dir() -> anyhow::Result<std::path::PathBuf> {
192        let config_dir = dirs::config_dir()
193            .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
194            .join("code-mesh");
195        
196        if !config_dir.exists() {
197            std::fs::create_dir_all(&config_dir)?;
198        }
199        
200        Ok(config_dir)
201    }
202
203    /// Get the default configuration file path
204    pub fn default_config_path() -> anyhow::Result<std::path::PathBuf> {
205        Ok(Self::config_dir()?.join("tui.json"))
206    }
207
208    /// Load configuration with fallback to defaults
209    pub fn load_or_default() -> Self {
210        match Self::default_config_path() {
211            Ok(path) => {
212                if path.exists() {
213                    match Self::load_from_file(&path) {
214                        Ok(config) => return config,
215                        Err(e) => {
216                            eprintln!("Failed to load config from {}: {}", path.display(), e);
217                        }
218                    }
219                }
220            }
221            Err(e) => {
222                eprintln!("Failed to get config path: {}", e);
223            }
224        }
225        Self::default()
226    }
227}