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