From 104e5bf62e5043a1b700fb63c051b179a814dec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Die=C3=9Fner?= <40034835+TheBlackSheep3@users.noreply.github.com> Date: Sat, 29 Jul 2023 23:04:53 +0200 Subject: [PATCH] 1751 follow symlink for keybinding config (#1767) --- CHANGELOG.md | 1 + src/keys/key_config.rs | 158 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9005d55..f94682f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixes * fix commit dialog char count for multibyte characters ([#1726](https://github.com/extrawurst/gitui/issues/1726)) * fix wrong hit highlighting in fuzzy find popup [[@UUGTech](https://github.com/UUGTech)] ([#1731](https://github.com/extrawurst/gitui/pull/1731)) +* fix symlink support for configuration files [[@TheBlackSheep3](https://github.com/TheBlackSheep3)] ([#1751](https://github.com/extrawurst/gitui/issues/1751)) ## [0.23.0] - 2022-06-19 diff --git a/src/keys/key_config.rs b/src/keys/key_config.rs index d9a4c51d..e48db11b 100644 --- a/src/keys/key_config.rs +++ b/src/keys/key_config.rs @@ -1,6 +1,6 @@ use anyhow::Result; use crossterm::event::{KeyCode, KeyModifiers}; -use std::{path::PathBuf, rc::Rc}; +use std::{fs::canonicalize, path::PathBuf, rc::Rc}; use crate::{args::get_app_config_path, strings::symbol}; @@ -10,6 +10,8 @@ use super::{ }; pub type SharedKeyConfig = Rc; +const KEY_LIST_FILENAME: &str = "key_bindings.ron"; +const KEY_SYMBOLS_FILENAME: &str = "key_symbols.ron"; #[derive(Default, Clone)] pub struct KeyConfig { @@ -20,12 +22,16 @@ pub struct KeyConfig { impl KeyConfig { fn get_config_file() -> Result { let app_home = get_app_config_path()?; - Ok(app_home.join("key_bindings.ron")) + let config_file = app_home.join(KEY_LIST_FILENAME); + canonicalize(&config_file) + .map_or_else(|_| Ok(config_file), Ok) } fn get_symbols_file() -> Result { let app_home = get_app_config_path()?; - Ok(app_home.join("key_symbols.ron")) + let symbols_file = app_home.join(KEY_SYMBOLS_FILENAME); + canonicalize(&symbols_file) + .map_or_else(|_| Ok(symbols_file), Ok) } pub fn init() -> Result { @@ -114,6 +120,9 @@ impl KeyConfig { mod tests { use super::*; use crossterm::event::{KeyCode, KeyModifiers}; + use std::fs; + use std::io::Write; + use tempfile::NamedTempFile; #[test] fn test_get_hint() { @@ -124,4 +133,147 @@ mod tests { )); assert_eq!(h, "^c"); } + + #[test] + fn test_symbolic_links() { + let app_home = get_app_config_path().unwrap(); + // save current config + let original_key_list_path = app_home.join(KEY_LIST_FILENAME); + let renamed_key_list = if original_key_list_path.exists() { + let temp = NamedTempFile::new_in(&app_home).unwrap(); + fs::rename(&original_key_list_path, &temp).unwrap(); + Some(temp) + } else { + None + }; + let original_key_symbols_path = + app_home.join(KEY_SYMBOLS_FILENAME); + let renamed_key_symbols = if original_key_symbols_path + .exists() + { + let temp = NamedTempFile::new_in(&app_home).unwrap(); + fs::rename(&original_key_symbols_path, &temp).unwrap(); + Some(temp) + } else { + None + }; + + // create temporary config files + let mut temporary_key_list = + NamedTempFile::new_in(&app_home).unwrap(); + writeln!( + temporary_key_list, + r" +( + move_down: Some(( code: Char('j'), modifiers: ( bits: 2,),)), +) +" + ) + .unwrap(); + + let mut temporary_key_symbols = + NamedTempFile::new_in(&app_home).unwrap(); + writeln!( + temporary_key_symbols, + " +( + esc: Some(\"Esc\"), +) +" + ) + .unwrap(); + + // testing + let result = std::panic::catch_unwind(|| { + let loaded_config = KeyConfig::init().unwrap(); + assert_eq!( + loaded_config.keys.move_down, + KeysList::default().move_down + ); + assert_eq!( + loaded_config.symbols.esc, + KeySymbols::default().esc + ); + + create_symlink( + &temporary_key_symbols, + &original_key_symbols_path, + ) + .unwrap(); + let loaded_config = KeyConfig::init().unwrap(); + assert_eq!( + loaded_config.keys.move_down, + KeysList::default().move_down + ); + assert_eq!(loaded_config.symbols.esc, "Esc"); + + create_symlink( + &temporary_key_list, + &original_key_list_path, + ) + .unwrap(); + let loaded_config = KeyConfig::init().unwrap(); + assert_eq!( + loaded_config.keys.move_down, + GituiKeyEvent::new( + KeyCode::Char('j'), + KeyModifiers::CONTROL + ) + ); + assert_eq!(loaded_config.symbols.esc, "Esc"); + + fs::remove_file(&original_key_symbols_path).unwrap(); + let loaded_config = KeyConfig::init().unwrap(); + assert_eq!( + loaded_config.keys.move_down, + GituiKeyEvent::new( + KeyCode::Char('j'), + KeyModifiers::CONTROL + ) + ); + assert_eq!( + loaded_config.symbols.esc, + KeySymbols::default().esc + ); + + fs::remove_file(&original_key_list_path).unwrap(); + }); + + // remove symlinks from testing if they still exist + let _ = fs::remove_file(&original_key_list_path); + let _ = fs::remove_file(&original_key_symbols_path); + + // restore original config files + if let Some(temp) = renamed_key_list { + let _ = fs::rename(&temp, &original_key_list_path); + } + + if let Some(temp) = renamed_key_symbols { + let _ = fs::rename(&temp, &original_key_symbols_path); + } + + assert!(result.is_ok()); + } + + #[cfg(not(target_os = "windows"))] + fn create_symlink< + P: AsRef, + Q: AsRef, + >( + original: P, + link: Q, + ) -> Result<(), std::io::Error> { + std::os::unix::fs::symlink(original, link) + } + + #[cfg(target_os = "windows")] + fn create_symlink< + P: AsRef, + Q: AsRef, + >( + original: P, + link: Q, + ) -> Result<(), std::io::Error> { + std::os::windows::fs::symlink_file(original, link) + } }