Skip to main content

panex_core/
config.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fs;
4use std::path::PathBuf;
5
6#[derive(Debug, Serialize, Deserialize, Clone, Default)]
7pub struct PanexConfig {
8    #[serde(default)]
9    pub favorites: FavoritesConfig,
10    #[serde(default)]
11    pub open: OpenConfig,
12}
13
14#[derive(Debug, Serialize, Deserialize, Clone, Default)]
15pub struct FavoritesConfig {
16    #[serde(default)]
17    pub paths: Vec<String>,
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone, Default)]
21pub struct OpenConfig {
22    #[serde(default)]
23    pub gui: HashMap<String, String>,
24    #[serde(default)]
25    pub tui: HashMap<String, String>,
26}
27
28impl PanexConfig {
29    /// Returns ~/.panex/config.toml
30    pub fn config_path() -> Result<PathBuf, String> {
31        let home = dirs::home_dir().ok_or("Could not determine home directory")?;
32        Ok(home.join(".panex").join("config.toml"))
33    }
34
35    /// Load config from disk, returning defaults if file doesn't exist.
36    pub fn load() -> Self {
37        let path = match Self::config_path() {
38            Ok(p) => p,
39            Err(_) => return Self::default(),
40        };
41        let content = match fs::read_to_string(&path) {
42            Ok(c) => c,
43            Err(_) => return Self::default(),
44        };
45        toml::from_str(&content).unwrap_or_default()
46    }
47
48    /// Save config to disk, creating ~/.panex/ if needed.
49    pub fn save(&self) -> Result<(), String> {
50        let path = Self::config_path()?;
51        if let Some(parent) = path.parent() {
52            fs::create_dir_all(parent)
53                .map_err(|e| format!("Failed to create config directory: {}", e))?;
54        }
55        let content =
56            toml::to_string_pretty(self).map_err(|e| format!("Failed to serialize config: {}", e))?;
57        fs::write(&path, content).map_err(|e| format!("Failed to write config: {}", e))
58    }
59
60    pub fn is_favorite(&self, path: &str) -> bool {
61        let normalized = normalize_path(path);
62        self.favorites.paths.iter().any(|p| normalize_path(p) == normalized)
63    }
64
65    pub fn add_favorite(&mut self, path: &str) -> Result<(), String> {
66        let normalized = normalize_path(path);
67        if !self.is_favorite(&normalized) {
68            self.favorites.paths.push(normalized);
69            self.save()?;
70        }
71        Ok(())
72    }
73
74    pub fn remove_favorite(&mut self, path: &str) -> Result<(), String> {
75        let normalized = normalize_path(path);
76        self.favorites.paths.retain(|p| normalize_path(p) != normalized);
77        self.save()
78    }
79
80    pub fn toggle_favorite(&mut self, path: &str) -> Result<bool, String> {
81        if self.is_favorite(path) {
82            self.remove_favorite(path)?;
83            Ok(false)
84        } else {
85            self.add_favorite(path)?;
86            Ok(true)
87        }
88    }
89
90    /// Get the custom app for a file extension in GUI mode.
91    pub fn get_gui_app(&self, ext: &str) -> Option<&String> {
92        let key = if ext.starts_with('.') {
93            ext.to_string()
94        } else {
95            format!(".{}", ext)
96        };
97        self.open.gui.get(&key)
98    }
99
100    /// Get the custom command for a file extension in TUI mode.
101    pub fn get_tui_app(&self, ext: &str) -> Option<&String> {
102        let key = if ext.starts_with('.') {
103            ext.to_string()
104        } else {
105            format!(".{}", ext)
106        };
107        self.open.tui.get(&key)
108    }
109}
110
111/// Expand ~ to home dir for comparison, and strip trailing slashes.
112fn normalize_path(path: &str) -> String {
113    let expanded = if path.starts_with('~') {
114        if let Some(home) = dirs::home_dir() {
115            path.replacen('~', &home.to_string_lossy(), 1)
116        } else {
117            path.to_string()
118        }
119    } else {
120        path.to_string()
121    };
122    expanded.trim_end_matches('/').to_string()
123}