diff --git a/CHANGELOG.md b/CHANGELOG.md index e60180cdc5..53799e3457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * add `use_selection_fg` to theme file to allow customizing selection foreground color [[@Upsylonbare](https://github.com/Upsylonbare)] ([#2515](https://github.com/gitui-org/gitui/pull/2515)) ### Changed +* Respect `XDG_CONFIG_HOME` and `XDG_CACHE_HOME` irrespective of OS [[@KlassyKat](https://github.com/KlassyKat)] ([#1498](https://github.com/gitui-org/gitui/issues/1498)) * improve error messages [[@acuteenvy](https://github.com/acuteenvy)] ([#2617](https://github.com/gitui-org/gitui/pull/2617)) * increase MSRV from 1.70 to 1.81 [[@naseschwarz](https://github.com/naseschwarz)] ([#2094](https://github.com/gitui-org/gitui/issues/2094)) * improve syntax highlighting file detection [[@acuteenvy](https://github.com/acuteenvy)] ([#2524](https://github.com/extrawurst/gitui/pull/2524)) diff --git a/KEY_CONFIG.md b/KEY_CONFIG.md index 658aad11b6..0fccb7fc14 100644 --- a/KEY_CONFIG.md +++ b/KEY_CONFIG.md @@ -19,12 +19,17 @@ Create a `key_bindings.ron` file like this: ) ``` -The config file format based on the [Ron file format](https://github.com/ron-rs/ron). +The key binding config file uses the [Ron file format](https://github.com/ron-rs/ron). The location of the file depends on your OS: -* `$HOME/.config/gitui/key_bindings.ron` (mac) -* `$XDG_CONFIG_HOME/gitui/key_bindings.ron` (linux using XDG) -* `$HOME/.config/gitui/key_bindings.ron` (linux) -* `%APPDATA%/gitui/key_bindings.ron` (Windows) +`gitui` will look for an existing `/gitui` in the following order: +* `$XDG_CONFIG_HOME/gitui/` (with `XDG_CONFIG_HOME` set) +* `$HOME/.config/gitui/` +* Default OS Location: + * `$HOME/Library/Application Support/` (mac) + * `$HOME/.config/gitui/` (linux) + * `%APPDATA%/gitui/` (Windows) + +Key bindings are configured in `key_bindings.ron` within your first found `gitui` config folder. See all possible keys to overwrite in gitui: [here](https://github.com/gitui-org/gitui/blob/master/src/keys/key_list.rs#L83) diff --git a/README.md b/README.md index dd4270d6ed..3067111396 100644 --- a/README.md +++ b/README.md @@ -250,9 +250,9 @@ see [FAQs page](./FAQ.md) To run with logging enabled run `gitui -l`. This will log to: - +- With `XDG_CACHE_HOME` set: `$XDG_CACHE_HOME/gitui/gitui.log` +or default to - macOS: `$HOME/Library/Caches/gitui/gitui.log` -- Linux using `XDG`: `$XDG_CACHE_HOME/gitui/gitui.log` - Linux: `$HOME/.cache/gitui/gitui.log` - Windows: `%LOCALAPPDATA%/gitui/gitui.log` diff --git a/THEMES.md b/THEMES.md index 52c235b496..376334d349 100644 --- a/THEMES.md +++ b/THEMES.md @@ -7,14 +7,19 @@ default on light terminal: To change the colors of the default theme you need to add a `theme.ron` file that contains the colors you want to override. Note that you don’t have to specify the full theme anymore (as of 0.23). Instead, it is sufficient to override just the values that you want to differ from their default values. -The file uses the [Ron format](https://github.com/ron-rs/ron) and is located at one of the following paths, depending on your operating system: - -* `$HOME/.config/gitui/theme.ron` (mac) -* `$XDG_CONFIG_HOME/gitui/theme.ron` (linux using XDG) -* `$HOME/.config/gitui/theme.ron` (linux) -* `%APPDATA%/gitui/theme.ron` (Windows) - -Alternatively, you can create a theme in the same directory mentioned above and use it with the `-t` flag followed by the name of the file in the directory. E.g. If you are on linux calling `gitui -t arc.ron`, this will load the theme in `$XDG_CONFIG_HOME/gitui/arc.ron` or `$HOME/.config/gitui/arc.ron`. +The theme file uses the [Ron file format](https://github.com/ron-rs/ron). +The location of the file depends on your OS: +`gitui` will look for an existing `/gitui` in the following order: +* `$XDG_CONFIG_HOME/gitui/` (with `XDG_CONFIG_HOME` set) +* `$HOME/.config/gitui/` +* Default OS Location: + * `$HOME/Library/Application Support/` (mac) + * `$HOME/.config/gitui/` (linux) + * `%APPDATA%/gitui/` (Windows) + +The theme is configured in `theme.ron` within your first found `gitui` config folder. + +Alternatively, you can create a theme in the same directory mentioned above and use it with the `-t` flag followed by the name of the file in the directory. E.g. Calling `gitui -t arc.ron` will load the `arc.ron` theme from your first found `/gitui` config folder using the logic above. Example theme override: diff --git a/src/args.rs b/src/args.rs index 4c1a64ec92..1630b2d949 100644 --- a/src/args.rs +++ b/src/args.rs @@ -49,7 +49,6 @@ pub fn process_cmdline() -> Result { .map_or_else(|| PathBuf::from("theme.ron"), PathBuf::from); let confpath = get_app_config_path()?; - fs::create_dir_all(&confpath)?; let theme = confpath.join(arg_theme); let notify_watcher: bool = @@ -149,28 +148,110 @@ fn setup_logging(path_override: Option) -> Result<()> { Ok(()) } +fn get_path_from_candidates( + candidates: impl IntoIterator>, +) -> Result { + let mut target_dir = None; + + // Filter into existing directories + for potential_dir in + candidates.into_iter().flatten().filter(|p| p.is_dir()) + { + let search_path = potential_dir.join("gitui"); + + // Prefer preexisting gitui directory + if search_path.is_dir() { + target_dir = Some(search_path); + break; + } + + // Fallback to first existing directory + target_dir.get_or_insert(search_path); + } + + target_dir.ok_or_else(|| { + anyhow!("failed to find valid path within candidates") + }) +} + +// "If an implementation encounters a relative path in any of these variables it should consider the path invalid and ignore it." +fn get_xdg_path(xdg_var: &str) -> Option { + env::var_os(xdg_var) + .filter(|var| !var.is_empty()) + .map(PathBuf::from) + .filter(|p| p.is_absolute()) +} + fn get_app_cache_path() -> Result { - let mut path = dirs::cache_dir() - .ok_or_else(|| anyhow!("failed to find os cache dir."))?; + let cache_dir_candidates = + [get_xdg_path("XDG_CACHE_HOME"), dirs::cache_dir()]; + + let cache_dir = get_path_from_candidates(cache_dir_candidates) + .map_err(|_| anyhow!("failed to find valid cache dir."))?; - path.push("gitui"); - fs::create_dir_all(&path)?; - Ok(path) + fs::create_dir_all(&cache_dir)?; + Ok(cache_dir) } pub fn get_app_config_path() -> Result { - let mut path = if cfg!(target_os = "macos") { - dirs::home_dir().map(|h| h.join(".config")) - } else { - dirs::config_dir() - } - .ok_or_else(|| anyhow!("failed to find os config dir."))?; + // List of potential config directories in order of priority + let config_dir_candidates = [ + get_xdg_path("XDG_CONFIG_HOME"), + // This is in the list since it was the hardcoded behavior on macos before + // I expect this to be what most people have XDG_CONFIG_HOME set to already + // But explicitly including this will avoid breaking anyone's existing config + dirs::home_dir().map(|p| p.join(".config")), + dirs::config_dir(), + ]; - path.push("gitui"); - Ok(path) + get_path_from_candidates(config_dir_candidates) + .map_err(|_| anyhow!("failed to find valid config dir.")) } -#[test] -fn verify_app() { - app().debug_assert(); +#[cfg(test)] +mod tests { + use std::fs; + + use super::{app, get_path_from_candidates}; + use tempfile::tempdir; + + #[test] + fn verify_app() { + app().debug_assert(); + } + + #[test] + fn test_config_dir_candidates_from_preexisting() { + let temp_dummy_1 = tempdir().expect("should create temp dir"); + let temp_dummy_2 = tempdir().expect("should create temp dir"); + let temp_target = tempdir().expect("should create temp dir"); + let temp_goal = temp_target.path().join("gitui"); + + fs::create_dir_all(&temp_goal) + .expect("should create temp target directory"); + + let candidates = [ + Some(temp_dummy_1.path().to_path_buf()), + Some(temp_target.path().to_path_buf()), + Some(temp_dummy_2.path().to_path_buf()), + ]; + let result = get_path_from_candidates(candidates) + .expect("should find the included target"); + assert_eq!(result, temp_goal); + } + + #[test] + fn test_config_dir_candidates_no_preexisting() { + let temp_dummy_1 = tempdir().expect("should create temp dir"); + let temp_dummy_2 = tempdir().expect("should create temp dir"); + + let candidates = [ + Some(temp_dummy_1.path().to_path_buf()), + Some(temp_dummy_2.path().to_path_buf()), + ]; + + let result = get_path_from_candidates(candidates) + .expect("should return first candidate"); + assert_eq!(result, temp_dummy_1.path().join("gitui")); + } }