Skip to main content

try_rs/
config.rs

1use crate::tui::Theme;
2use crate::utils::expand_path;
3use serde::Deserialize;
4use serde::Serialize;
5use std::fs;
6use std::io::Write;
7use std::path::{Path, PathBuf};
8
9#[derive(Deserialize, Serialize)]
10pub struct Config {
11    pub tries_paths: Option<String>,
12    pub tries_path: Option<String>,
13    pub theme: Option<String>,
14    pub editor: Option<String>,
15    pub apply_date_prefix: Option<bool>,
16    pub transparent_background: Option<bool>,
17    pub show_disk: Option<bool>,
18    pub show_preview: Option<bool>,
19    pub show_legend: Option<bool>,
20    pub show_right_panel: Option<bool>,
21    pub right_panel_width: Option<u16>,
22}
23
24pub fn get_file_config_toml_name() -> String {
25    std::env::var("TRY_CONFIG").unwrap_or("config.toml".to_string())
26}
27
28pub fn get_config_dir() -> PathBuf {
29    std::env::var_os("TRY_CONFIG_DIR")
30        .map(PathBuf::from)
31        .unwrap_or_else(|| get_base_config_dir().join("try-rs"))
32}
33
34/// Returns the base configuration directory.
35/// Respects $XDG_CONFIG_HOME on all platforms (including macOS),
36/// falling back to the platform-specific default from `dirs::config_dir()`,
37/// and finally to `~/.config`.
38pub fn get_base_config_dir() -> PathBuf {
39    std::env::var_os("XDG_CONFIG_HOME")
40        .map(PathBuf::from)
41        .or_else(dirs::config_dir)
42        .unwrap_or_else(|| {
43            dirs::home_dir()
44                .expect("Could not find home directory")
45                .join(".config")
46        })
47}
48
49/// Returns candidate config file paths in priority order.
50fn config_candidates() -> Vec<PathBuf> {
51    let config_name = get_file_config_toml_name();
52    let mut candidates = Vec::new();
53
54    if let Some(env_dir) = std::env::var_os("TRY_CONFIG_DIR") {
55        candidates.push(PathBuf::from(env_dir).join(&config_name));
56    }
57    let base_dir = get_base_config_dir();
58    candidates.push(base_dir.join("try-rs").join(&config_name));
59    if let Some(home) = dirs::home_dir() {
60        candidates.push(home.join(".config").join("try-rs").join(&config_name));
61    }
62
63    candidates
64}
65
66/// Finds the first existing config file path.
67fn find_config_path() -> Option<PathBuf> {
68    config_candidates().into_iter().find(|p| p.exists())
69}
70
71pub fn load_file_config_toml_if_exists() -> Option<Config> {
72    let path = find_config_path()?;
73    let contents = fs::read_to_string(&path).ok()?;
74    toml::from_str::<Config>(&contents).ok()
75}
76
77pub struct AppConfig {
78    pub tries_dirs: Vec<PathBuf>,
79    pub active_tab: usize,
80    pub theme: Theme,
81    pub editor_cmd: Option<String>,
82    pub config_path: Option<PathBuf>,
83    pub apply_date_prefix: Option<bool>,
84    pub transparent_background: Option<bool>,
85    pub show_disk: Option<bool>,
86    pub show_preview: Option<bool>,
87    pub show_legend: Option<bool>,
88    pub show_right_panel: Option<bool>,
89    pub right_panel_width: Option<u16>,
90}
91
92pub fn load_configuration() -> AppConfig {
93    let default_path = dirs::home_dir()
94        .expect("Folder not found")
95        .join("work")
96        .join("tries");
97
98    let mut theme = Theme::default();
99    let try_path = std::env::var_os("TRY_PATH");
100    let try_path_specified = try_path.is_some();
101    let mut final_paths: Vec<PathBuf> = if let Some(path) = try_path {
102        vec![path.into()]
103    } else {
104        vec![default_path]
105    };
106    let mut editor_cmd = std::env::var("VISUAL")
107        .ok()
108        .or_else(|| std::env::var("EDITOR").ok());
109    let mut apply_date_prefix = None;
110    let mut transparent_background = None;
111    let mut show_disk = None;
112    let mut show_preview = None;
113    let mut show_legend = None;
114    let mut show_right_panel = None;
115    let mut right_panel_width = None;
116
117    let loaded_config_path = find_config_path();
118
119    if let Some(config) = load_file_config_toml_if_exists() {
120        let paths_source = config.tries_paths.or(config.tries_path);
121        
122        if let Some(paths_str) = paths_source
123            && !try_path_specified
124        {
125            final_paths = paths_str
126                .split(',')
127                .map(|s| s.trim())
128                .filter(|s| !s.is_empty())
129                .map(expand_path)
130                .collect();
131        }
132        if let Some(editor) = config.editor {
133            editor_cmd = Some(editor);
134        }
135        if let Some(theme_name) = config.theme {
136            if let Some(found_theme) = Theme::all().into_iter().find(|t| t.name == theme_name) {
137                theme = found_theme;
138            }
139        }
140        apply_date_prefix = config.apply_date_prefix;
141        transparent_background = config.transparent_background;
142        show_disk = config.show_disk;
143        show_preview = config.show_preview;
144        show_legend = config.show_legend;
145        show_right_panel = config.show_right_panel;
146        right_panel_width = config.right_panel_width;
147    }
148
149    AppConfig {
150        tries_dirs: final_paths,
151        active_tab: 0,
152        theme,
153        editor_cmd,
154        config_path: loaded_config_path,
155        apply_date_prefix,
156        transparent_background,
157        show_disk,
158        show_preview,
159        show_legend,
160        show_right_panel,
161        right_panel_width,
162    }
163}
164
165pub fn save_config(
166    path: &Path,
167    theme: &Theme,
168    tries_paths: &[PathBuf],
169    editor: &Option<String>,
170    apply_date_prefix: Option<bool>,
171    transparent_background: Option<bool>,
172    show_disk: Option<bool>,
173    show_preview: Option<bool>,
174    show_legend: Option<bool>,
175    show_right_panel: Option<bool>,
176    right_panel_width: Option<u16>,
177) -> std::io::Result<()> {
178    let paths_string = tries_paths
179        .iter()
180        .map(|p| p.to_string_lossy().to_string())
181        .collect::<Vec<_>>()
182        .join(", ");
183
184    let config = Config {
185        tries_paths: Some(paths_string),
186        tries_path: tries_paths.first().map(|p| p.to_string_lossy().to_string()),
187        theme: Some(theme.name.clone()),
188        editor: editor.clone(),
189        apply_date_prefix,
190        transparent_background,
191        show_disk,
192        show_preview,
193        show_legend,
194        show_right_panel,
195        right_panel_width,
196    };
197
198    let toml_string =
199        toml::to_string(&config).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
200
201    if let Some(parent) = path.parent() {
202        if !parent.exists() {
203            fs::create_dir_all(parent)?;
204        }
205    }
206
207    let mut file = fs::File::create(path)?;
208    file.write_all(toml_string.as_bytes())?;
209    Ok(())
210}