1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Config {
7 pub theme: ThemeConfig,
9 pub keybinds: KeybindConfig,
11 pub file_viewer: FileViewerConfig,
13 pub chat: ChatConfig,
15 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#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ThemeConfig {
34 pub name: String,
36 pub custom: Option<CustomTheme>,
38 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#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct CustomTheme {
55 pub colors: HashMap<String, String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct KeybindConfig {
61 pub leader: String,
63 pub bindings: HashMap<String, String>,
65}
66
67impl Default for KeybindConfig {
68 fn default() -> Self {
69 let mut bindings = HashMap::new();
70
71 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct FileViewerConfig {
102 pub max_file_size: usize,
104 pub syntax_highlighting: bool,
106 pub show_line_numbers: bool,
108 pub word_wrap: bool,
110}
111
112impl Default for FileViewerConfig {
113 fn default() -> Self {
114 Self {
115 max_file_size: 10 * 1024 * 1024, syntax_highlighting: true,
117 show_line_numbers: true,
118 word_wrap: false,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ChatConfig {
126 pub max_messages: usize,
128 pub enable_history: bool,
130 pub auto_scroll: bool,
132 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#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct DiffConfig {
150 pub default_style: DiffStyle,
152 pub intra_line_highlighting: bool,
154 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#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
170pub enum DiffStyle {
171 Unified,
172 SideBySide,
173}
174
175impl Config {
176 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 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 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 pub fn default_config_path() -> anyhow::Result<std::path::PathBuf> {
205 Ok(Self::config_dir()?.join("tui.json"))
206 }
207
208 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}