Skip to main content

oracle_lib/config/
settings.rs

1//! Application settings and configuration
2
3use crate::error::Result;
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Settings {
9    pub ui: UiSettings,
10    pub analyzer: AnalyzerSettings,
11    pub keybindings: KeybindingSettings,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct UiSettings {
16    pub theme: String,
17    pub show_line_numbers: bool,
18    pub vim_mode: bool,
19    pub tab_width: usize,
20    pub wrap_text: bool,
21    pub accent_color: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AnalyzerSettings {
26    pub include_private: bool,
27    pub include_tests: bool,
28    pub max_depth: usize,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct KeybindingSettings {
33    pub quit: String,
34    pub search: String,
35    pub help: String,
36    pub next_tab: String,
37    pub prev_tab: String,
38    pub select: String,
39}
40
41impl Default for Settings {
42    fn default() -> Self {
43        Self {
44            ui: UiSettings {
45                theme: "default".into(),
46                show_line_numbers: true,
47                vim_mode: false,
48                tab_width: 4,
49                wrap_text: false,
50                accent_color: "#4EBF71".into(),
51            },
52            analyzer: AnalyzerSettings {
53                include_private: true,
54                include_tests: false,
55                max_depth: 10,
56            },
57            keybindings: KeybindingSettings {
58                quit: "q".into(),
59                search: "/".into(),
60                help: "?".into(),
61                next_tab: "Tab".into(),
62                prev_tab: "Shift+Tab".into(),
63                select: "Enter".into(),
64            },
65        }
66    }
67}
68
69impl Settings {
70    pub fn load() -> Result<Self> {
71        let config_path = Self::config_path()?;
72
73        if !config_path.exists() {
74            return Ok(Self::default());
75        }
76
77        let content = std::fs::read_to_string(&config_path)?;
78        let settings: Settings = serde_yaml::from_str(&content)?;
79        Ok(settings)
80    }
81
82    pub fn save(&self) -> Result<()> {
83        let config_path = Self::config_path()?;
84
85        if let Some(parent) = config_path.parent() {
86            std::fs::create_dir_all(parent)?;
87        }
88
89        let content = serde_yaml::to_string(self)?;
90        std::fs::write(&config_path, content)?;
91        Ok(())
92    }
93
94    fn config_path() -> Result<PathBuf> {
95        let config_dir = dirs::config_dir()
96            .ok_or_else(|| crate::error::OracleError::Config("No config directory".into()))?;
97        Ok(config_dir.join("oracle").join("config.yaml"))
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_settings_default() {
107        let s = Settings::default();
108        assert_eq!(s.ui.theme, "default");
109        assert!(s.ui.show_line_numbers);
110        assert_eq!(s.keybindings.quit, "q");
111        assert_eq!(s.keybindings.search, "/");
112        assert!(s.analyzer.include_private);
113        assert_eq!(s.analyzer.max_depth, 10);
114    }
115
116    #[test]
117    fn test_settings_roundtrip_yaml() {
118        let s = Settings::default();
119        let yaml = serde_yaml::to_string(&s).unwrap();
120        let loaded: Settings = serde_yaml::from_str(&yaml).unwrap();
121        assert_eq!(s.ui.theme, loaded.ui.theme);
122        assert_eq!(s.keybindings.quit, loaded.keybindings.quit);
123    }
124}