use anyhow::Result; use asyncgit::sync::{ diff::DiffOptions, repo_dir, RepoPathRef, ShowUntrackedFilesConfig, }; use ron::{ de::from_bytes, ser::{to_string_pretty, PrettyConfig}, }; use serde::{Deserialize, Serialize}; use std::{ cell::RefCell, fs::File, io::{Read, Write}, path::PathBuf, rc::Rc, }; #[derive(Default, Clone, Serialize, Deserialize)] struct OptionsData { pub tab: usize, pub diff: DiffOptions, pub status_show_untracked: Option, pub commit_msgs: Vec, } const COMMIT_MSG_HISTRY_LENGTH: usize = 20; #[derive(Clone)] pub struct Options { repo: RepoPathRef, data: OptionsData, } pub type SharedOptions = Rc>; impl Options { pub fn new(repo: RepoPathRef) -> SharedOptions { Rc::new(RefCell::new(Self { data: Self::read(&repo).unwrap_or_default(), repo, })) } pub fn set_current_tab(&mut self, tab: usize) { self.data.tab = tab; self.save(); } pub const fn current_tab(&self) -> usize { self.data.tab } pub const fn diff_options(&self) -> DiffOptions { self.data.diff } pub const fn status_show_untracked( &self, ) -> Option { self.data.status_show_untracked } pub fn set_status_show_untracked( &mut self, value: Option, ) { self.data.status_show_untracked = value; self.save(); } pub fn diff_context_change(&mut self, increase: bool) { self.data.diff.context = if increase { self.data.diff.context.saturating_add(1) } else { self.data.diff.context.saturating_sub(1) }; self.save(); } pub fn diff_hunk_lines_change(&mut self, increase: bool) { self.data.diff.interhunk_lines = if increase { self.data.diff.interhunk_lines.saturating_add(1) } else { self.data.diff.interhunk_lines.saturating_sub(1) }; self.save(); } pub fn diff_toggle_whitespace(&mut self) { self.data.diff.ignore_whitespace = !self.data.diff.ignore_whitespace; self.save(); } pub fn add_commit_msg(&mut self, msg: &str) { self.data.commit_msgs.push(msg.to_owned()); while self.data.commit_msgs.len() > COMMIT_MSG_HISTRY_LENGTH { self.data.commit_msgs.remove(0); } self.save(); } pub fn commit_msg(&self, idx: usize) -> Option { if self.data.commit_msgs.is_empty() { None } else { Some( self.data.commit_msgs [idx % self.data.commit_msgs.len()] .to_string(), ) } } fn save(&self) { if let Err(e) = self.save_failable() { log::error!("options save error: {}", e); } } fn read(repo: &RepoPathRef) -> Result { let dir = Self::options_file(repo)?; let mut f = File::open(dir)?; let mut buffer = Vec::new(); f.read_to_end(&mut buffer)?; Ok(from_bytes(&buffer)?) } //TODO: fix once FP in clippy is fixed #[allow(clippy::needless_borrow)] fn save_failable(&self) -> Result<()> { let dir = Self::options_file(&self.repo)?; let mut file = File::create(&dir)?; let data = to_string_pretty(&self.data, PrettyConfig::default())?; file.write_all(data.as_bytes())?; Ok(()) } fn options_file(repo: &RepoPathRef) -> Result { let dir = repo_dir(&repo.borrow())?; let dir = dir.join("gitui"); Ok(dir) } }