diff --git a/.vscode/settings.json b/.vscode/settings.json index a9c40cf5..bf7d5340 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { "editor.formatOnSave": true, "workbench.settings.enableNaturalLanguageSearch": false, - "telemetry.enableTelemetry": false, } \ No newline at end of file diff --git a/asyncgit/src/asyncjob/mod.rs b/asyncgit/src/asyncjob/mod.rs index 3b48787c..161df802 100644 --- a/asyncgit/src/asyncjob/mod.rs +++ b/asyncgit/src/asyncjob/mod.rs @@ -8,202 +8,202 @@ use std::sync::{Arc, Mutex}; /// trait that defines an async task we can run on a threadpool pub trait AsyncJob: Send + Sync + Clone { - /// can run a synchronous time intensive task - fn run(&mut self); + /// can run a synchronous time intensive task + fn run(&mut self); } /// Abstraction for a FIFO task queue that will only queue up **one** `next` job. /// It keeps overwriting the next job until it is actually taken to be processed #[derive(Debug, Clone)] pub struct AsyncSingleJob { - next: Arc>>, - last: Arc>>, - sender: Sender, - pending: Arc>, - notification: T, + next: Arc>>, + last: Arc>>, + sender: Sender, + pending: Arc>, + notification: T, } impl - AsyncSingleJob + AsyncSingleJob { - /// - pub fn new(sender: Sender, value: T) -> Self { - Self { - next: Arc::new(Mutex::new(None)), - last: Arc::new(Mutex::new(None)), - pending: Arc::new(Mutex::new(())), - notification: value, - sender, - } - } + /// + pub fn new(sender: Sender, value: T) -> Self { + Self { + next: Arc::new(Mutex::new(None)), + last: Arc::new(Mutex::new(None)), + pending: Arc::new(Mutex::new(())), + notification: value, + sender, + } + } - /// - pub fn is_pending(&self) -> bool { - self.pending.try_lock().is_err() - } + /// + pub fn is_pending(&self) -> bool { + self.pending.try_lock().is_err() + } - /// makes sure `next` is cleared and returns `true` if it actually canceled something - pub fn cancel(&mut self) -> bool { - if let Ok(mut next) = self.next.lock() { - if next.is_some() { - *next = None; - return true; - } - } + /// makes sure `next` is cleared and returns `true` if it actually canceled something + pub fn cancel(&mut self) -> bool { + if let Ok(mut next) = self.next.lock() { + if next.is_some() { + *next = None; + return true; + } + } - false - } + false + } - /// take out last finished job - pub fn take_last(&self) -> Option { - if let Ok(mut last) = self.last.lock() { - last.take() - } else { - None - } - } + /// take out last finished job + pub fn take_last(&self) -> Option { + if let Ok(mut last) = self.last.lock() { + last.take() + } else { + None + } + } - /// spawns `task` if nothing is running currently, otherwise schedules as `next` overwriting if `next` was set before - pub fn spawn(&mut self, task: J) -> bool { - self.schedule_next(task); - self.check_for_job() - } + /// spawns `task` if nothing is running currently, otherwise schedules as `next` overwriting if `next` was set before + pub fn spawn(&mut self, task: J) -> bool { + self.schedule_next(task); + self.check_for_job() + } - fn check_for_job(&self) -> bool { - if self.is_pending() { - return false; - } + fn check_for_job(&self) -> bool { + if self.is_pending() { + return false; + } - if let Some(task) = self.take_next() { - let self_arc = self.clone(); + if let Some(task) = self.take_next() { + let self_arc = self.clone(); - rayon_core::spawn(move || { - if let Err(e) = self_arc.run_job(task) { - log::error!("async job error: {}", e); - } - }); + rayon_core::spawn(move || { + if let Err(e) = self_arc.run_job(task) { + log::error!("async job error: {}", e); + } + }); - return true; - } + return true; + } - false - } + false + } - fn run_job(&self, mut task: J) -> Result<()> { - //limit the pending scope - { - let _pending = self.pending.lock()?; + fn run_job(&self, mut task: J) -> Result<()> { + //limit the pending scope + { + let _pending = self.pending.lock()?; - task.run(); + task.run(); - if let Ok(mut last) = self.last.lock() { - *last = Some(task); - } + if let Ok(mut last) = self.last.lock() { + *last = Some(task); + } - self.sender.send(self.notification)?; - } + self.sender.send(self.notification)?; + } - self.check_for_job(); + self.check_for_job(); - Ok(()) - } + Ok(()) + } - fn schedule_next(&mut self, task: J) { - if let Ok(mut next) = self.next.lock() { - *next = Some(task); - } - } + fn schedule_next(&mut self, task: J) { + if let Ok(mut next) = self.next.lock() { + *next = Some(task); + } + } - fn take_next(&self) -> Option { - if let Ok(mut next) = self.next.lock() { - next.take() - } else { - None - } - } + fn take_next(&self) -> Option { + if let Ok(mut next) = self.next.lock() { + next.take() + } else { + None + } + } } #[cfg(test)] mod test { - use super::*; - use crossbeam_channel::unbounded; - use pretty_assertions::assert_eq; - use std::{ - sync::atomic::AtomicU32, thread::sleep, time::Duration, - }; + use super::*; + use crossbeam_channel::unbounded; + use pretty_assertions::assert_eq; + use std::{ + sync::atomic::AtomicU32, thread::sleep, time::Duration, + }; - #[derive(Clone)] - struct TestJob { - v: Arc, - value_to_add: u32, - } + #[derive(Clone)] + struct TestJob { + v: Arc, + value_to_add: u32, + } - impl AsyncJob for TestJob { - fn run(&mut self) { - sleep(Duration::from_millis(100)); + impl AsyncJob for TestJob { + fn run(&mut self) { + sleep(Duration::from_millis(100)); - self.v.fetch_add( - self.value_to_add, - std::sync::atomic::Ordering::Relaxed, - ); - } - } + self.v.fetch_add( + self.value_to_add, + std::sync::atomic::Ordering::Relaxed, + ); + } + } - type Notificaton = (); + type Notificaton = (); - #[test] - fn test_overwrite() { - let (sender, receiver) = unbounded(); + #[test] + fn test_overwrite() { + let (sender, receiver) = unbounded(); - let mut job: AsyncSingleJob = - AsyncSingleJob::new(sender, ()); + let mut job: AsyncSingleJob = + AsyncSingleJob::new(sender, ()); - let task = TestJob { - v: Arc::new(AtomicU32::new(1)), - value_to_add: 1, - }; + let task = TestJob { + v: Arc::new(AtomicU32::new(1)), + value_to_add: 1, + }; - assert!(job.spawn(task.clone())); - sleep(Duration::from_millis(1)); - for _ in 0..5 { - assert!(!job.spawn(task.clone())); - } + assert!(job.spawn(task.clone())); + sleep(Duration::from_millis(1)); + for _ in 0..5 { + assert!(!job.spawn(task.clone())); + } - let _foo = receiver.recv().unwrap(); - let _foo = receiver.recv().unwrap(); - assert!(receiver.is_empty()); + let _foo = receiver.recv().unwrap(); + let _foo = receiver.recv().unwrap(); + assert!(receiver.is_empty()); - assert_eq!( - task.v.load(std::sync::atomic::Ordering::Relaxed), - 3 - ); - } + assert_eq!( + task.v.load(std::sync::atomic::Ordering::Relaxed), + 3 + ); + } - #[test] - fn test_cancel() { - let (sender, receiver) = unbounded(); + #[test] + fn test_cancel() { + let (sender, receiver) = unbounded(); - let mut job: AsyncSingleJob = - AsyncSingleJob::new(sender, ()); + let mut job: AsyncSingleJob = + AsyncSingleJob::new(sender, ()); - let task = TestJob { - v: Arc::new(AtomicU32::new(1)), - value_to_add: 1, - }; + let task = TestJob { + v: Arc::new(AtomicU32::new(1)), + value_to_add: 1, + }; - assert!(job.spawn(task.clone())); - sleep(Duration::from_millis(1)); + assert!(job.spawn(task.clone())); + sleep(Duration::from_millis(1)); - for _ in 0..5 { - assert!(!job.spawn(task.clone())); - } - assert!(job.cancel()); + for _ in 0..5 { + assert!(!job.spawn(task.clone())); + } + assert!(job.cancel()); - let _foo = receiver.recv().unwrap(); + let _foo = receiver.recv().unwrap(); - assert_eq!( - task.v.load(std::sync::atomic::Ordering::Relaxed), - 2 - ); - } + assert_eq!( + task.v.load(std::sync::atomic::Ordering::Relaxed), + 2 + ); + } } diff --git a/asyncgit/src/blame.rs b/asyncgit/src/blame.rs index 9bacfab8..9a2d562f 100644 --- a/asyncgit/src/blame.rs +++ b/asyncgit/src/blame.rs @@ -1,179 +1,179 @@ use crate::{ - error::Result, - hash, - sync::{self, FileBlame}, - AsyncGitNotification, CWD, + error::Result, + hash, + sync::{self, FileBlame}, + AsyncGitNotification, CWD, }; use crossbeam_channel::Sender; use std::{ - hash::Hash, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, - }, + hash::Hash, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, }; /// #[derive(Hash, Clone, PartialEq)] pub struct BlameParams { - /// path to the file to blame - pub file_path: String, + /// path to the file to blame + pub file_path: String, } struct Request(R, Option); #[derive(Default, Clone)] struct LastResult { - params: P, - hash: u64, - result: R, + params: P, + hash: u64, + result: R, } /// pub struct AsyncBlame { - current: Arc>>, - last: Arc>>>, - sender: Sender, - pending: Arc, + current: Arc>>, + last: Arc>>>, + sender: Sender, + pending: Arc, } impl AsyncBlame { - /// - pub fn new(sender: &Sender) -> Self { - Self { - current: Arc::new(Mutex::new(Request(0, None))), - last: Arc::new(Mutex::new(None)), - sender: sender.clone(), - pending: Arc::new(AtomicUsize::new(0)), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + current: Arc::new(Mutex::new(Request(0, None))), + last: Arc::new(Mutex::new(None)), + sender: sender.clone(), + pending: Arc::new(AtomicUsize::new(0)), + } + } - /// - pub fn last( - &mut self, - ) -> Result> { - let last = self.last.lock()?; + /// + pub fn last( + &mut self, + ) -> Result> { + let last = self.last.lock()?; - Ok(last.clone().map(|last_result| { - (last_result.params, last_result.result) - })) - } + Ok(last.clone().map(|last_result| { + (last_result.params, last_result.result) + })) + } - /// - pub fn refresh(&mut self) -> Result<()> { - if let Ok(Some(param)) = self.get_last_param() { - self.clear_current()?; - self.request(param)?; - } - Ok(()) - } + /// + pub fn refresh(&mut self) -> Result<()> { + if let Ok(Some(param)) = self.get_last_param() { + self.clear_current()?; + self.request(param)?; + } + Ok(()) + } - /// - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) > 0 - } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) > 0 + } - /// - pub fn request( - &mut self, - params: BlameParams, - ) -> Result> { - log::trace!("request"); + /// + pub fn request( + &mut self, + params: BlameParams, + ) -> Result> { + log::trace!("request"); - let hash = hash(¶ms); + let hash = hash(¶ms); - { - let mut current = self.current.lock()?; + { + let mut current = self.current.lock()?; - if current.0 == hash { - return Ok(current.1.clone()); - } + if current.0 == hash { + return Ok(current.1.clone()); + } - current.0 = hash; - current.1 = None; - } + current.0 = hash; + current.1 = None; + } - let arc_current = Arc::clone(&self.current); - let arc_last = Arc::clone(&self.last); - let sender = self.sender.clone(); - let arc_pending = Arc::clone(&self.pending); + let arc_current = Arc::clone(&self.current); + let arc_last = Arc::clone(&self.last); + let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); - self.pending.fetch_add(1, Ordering::Relaxed); + self.pending.fetch_add(1, Ordering::Relaxed); - rayon_core::spawn(move || { - let notify = Self::get_blame_helper( - params, - &arc_last, - &arc_current, - hash, - ); + rayon_core::spawn(move || { + let notify = Self::get_blame_helper( + params, + &arc_last, + &arc_current, + hash, + ); - let notify = match notify { - Err(err) => { - log::error!("get_blame_helper error: {}", err); - true - } - Ok(notify) => notify, - }; + let notify = match notify { + Err(err) => { + log::error!("get_blame_helper error: {}", err); + true + } + Ok(notify) => notify, + }; - arc_pending.fetch_sub(1, Ordering::Relaxed); + arc_pending.fetch_sub(1, Ordering::Relaxed); - sender - .send(if notify { - AsyncGitNotification::Blame - } else { - AsyncGitNotification::FinishUnchanged - }) - .expect("error sending blame"); - }); + sender + .send(if notify { + AsyncGitNotification::Blame + } else { + AsyncGitNotification::FinishUnchanged + }) + .expect("error sending blame"); + }); - Ok(None) - } + Ok(None) + } - fn get_blame_helper( - params: BlameParams, - arc_last: &Arc< - Mutex>>, - >, - arc_current: &Arc>>, - hash: u64, - ) -> Result { - let file_blame = - sync::blame::blame_file(CWD, ¶ms.file_path)?; + fn get_blame_helper( + params: BlameParams, + arc_last: &Arc< + Mutex>>, + >, + arc_current: &Arc>>, + hash: u64, + ) -> Result { + let file_blame = + sync::blame::blame_file(CWD, ¶ms.file_path)?; - let mut notify = false; - { - let mut current = arc_current.lock()?; - if current.0 == hash { - current.1 = Some(file_blame.clone()); - notify = true; - } - } + let mut notify = false; + { + let mut current = arc_current.lock()?; + if current.0 == hash { + current.1 = Some(file_blame.clone()); + notify = true; + } + } - { - let mut last = arc_last.lock()?; - *last = Some(LastResult { - result: file_blame, - hash, - params, - }); - } + { + let mut last = arc_last.lock()?; + *last = Some(LastResult { + result: file_blame, + hash, + params, + }); + } - Ok(notify) - } + Ok(notify) + } - fn get_last_param(&self) -> Result> { - Ok(self - .last - .lock()? - .clone() - .map(|last_result| last_result.params)) - } + fn get_last_param(&self) -> Result> { + Ok(self + .last + .lock()? + .clone() + .map(|last_result| last_result.params)) + } - fn clear_current(&mut self) -> Result<()> { - let mut current = self.current.lock()?; - current.0 = 0; - current.1 = None; - Ok(()) - } + fn clear_current(&mut self) -> Result<()> { + let mut current = self.current.lock()?; + current.0 = 0; + current.1 = None; + Ok(()) + } } diff --git a/asyncgit/src/cached/branchname.rs b/asyncgit/src/cached/branchname.rs index 50ec7975..d5c53546 100644 --- a/asyncgit/src/cached/branchname.rs +++ b/asyncgit/src/cached/branchname.rs @@ -1,48 +1,48 @@ use crate::{ - error::Result, - sync::{self, branch::get_branch_name}, + error::Result, + sync::{self, branch::get_branch_name}, }; use sync::Head; /// pub struct BranchName { - last_result: Option<(Head, String)>, - repo_path: String, + last_result: Option<(Head, String)>, + repo_path: String, } impl BranchName { - /// - pub fn new(path: &str) -> Self { - Self { - repo_path: path.to_string(), - last_result: None, - } - } + /// + pub fn new(path: &str) -> Self { + Self { + repo_path: path.to_string(), + last_result: None, + } + } - /// - pub fn lookup(&mut self) -> Result { - let current_head = - sync::get_head_tuple(self.repo_path.as_str())?; + /// + pub fn lookup(&mut self) -> Result { + let current_head = + sync::get_head_tuple(self.repo_path.as_str())?; - if let Some((last_head, branch_name)) = - self.last_result.as_ref() - { - if *last_head == current_head { - return Ok(branch_name.clone()); - } - } + if let Some((last_head, branch_name)) = + self.last_result.as_ref() + { + if *last_head == current_head { + return Ok(branch_name.clone()); + } + } - self.fetch(current_head) - } + self.fetch(current_head) + } - /// - pub fn last(&self) -> Option { - self.last_result.as_ref().map(|last| last.1.clone()) - } + /// + pub fn last(&self) -> Option { + self.last_result.as_ref().map(|last| last.1.clone()) + } - fn fetch(&mut self, head: Head) -> Result { - let name = get_branch_name(self.repo_path.as_str())?; - self.last_result = Some((head, name.clone())); - Ok(name) - } + fn fetch(&mut self, head: Head) -> Result { + let name = get_branch_name(self.repo_path.as_str())?; + self.last_result = Some((head, name.clone())); + Ok(name) + } } diff --git a/asyncgit/src/commit_files.rs b/asyncgit/src/commit_files.rs index a0948312..1bc61725 100644 --- a/asyncgit/src/commit_files.rs +++ b/asyncgit/src/commit_files.rs @@ -1,12 +1,12 @@ use crate::{ - error::Result, - sync::{self, CommitId}, - AsyncGitNotification, StatusItem, CWD, + error::Result, + sync::{self, CommitId}, + AsyncGitNotification, StatusItem, CWD, }; use crossbeam_channel::Sender; use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, }; type ResultType = Vec; @@ -14,92 +14,92 @@ struct Request(R, A); /// pub struct AsyncCommitFiles { - current: Arc>>>, - sender: Sender, - pending: Arc, + current: Arc>>>, + sender: Sender, + pending: Arc, } impl AsyncCommitFiles { - /// - pub fn new(sender: &Sender) -> Self { - Self { - current: Arc::new(Mutex::new(None)), - sender: sender.clone(), - pending: Arc::new(AtomicUsize::new(0)), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + current: Arc::new(Mutex::new(None)), + sender: sender.clone(), + pending: Arc::new(AtomicUsize::new(0)), + } + } - /// - pub fn current( - &mut self, - ) -> Result> { - let c = self.current.lock()?; + /// + pub fn current( + &mut self, + ) -> Result> { + let c = self.current.lock()?; - c.as_ref() - .map_or(Ok(None), |c| Ok(Some((c.0, c.1.clone())))) - } + c.as_ref() + .map_or(Ok(None), |c| Ok(Some((c.0, c.1.clone())))) + } - /// - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) > 0 - } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) > 0 + } - /// - pub fn fetch(&mut self, id: CommitId) -> Result<()> { - if self.is_pending() { - return Ok(()); - } + /// + pub fn fetch(&mut self, id: CommitId) -> Result<()> { + if self.is_pending() { + return Ok(()); + } - log::trace!("request: {}", id.to_string()); + log::trace!("request: {}", id.to_string()); - { - let current = self.current.lock()?; - if let Some(c) = &*current { - if c.0 == id { - return Ok(()); - } - } - } + { + let current = self.current.lock()?; + if let Some(c) = &*current { + if c.0 == id { + return Ok(()); + } + } + } - let arc_current = Arc::clone(&self.current); - let sender = self.sender.clone(); - let arc_pending = Arc::clone(&self.pending); + let arc_current = Arc::clone(&self.current); + let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); - self.pending.fetch_add(1, Ordering::Relaxed); + self.pending.fetch_add(1, Ordering::Relaxed); - rayon_core::spawn(move || { - Self::fetch_helper(id, &arc_current) - .expect("failed to fetch"); + rayon_core::spawn(move || { + Self::fetch_helper(id, &arc_current) + .expect("failed to fetch"); - arc_pending.fetch_sub(1, Ordering::Relaxed); + arc_pending.fetch_sub(1, Ordering::Relaxed); - sender - .send(AsyncGitNotification::CommitFiles) - .expect("error sending"); - }); + sender + .send(AsyncGitNotification::CommitFiles) + .expect("error sending"); + }); - Ok(()) - } + Ok(()) + } - fn fetch_helper( - id: CommitId, - arc_current: &Arc< - Mutex>>, - >, - ) -> Result<()> { - let res = sync::get_commit_files(CWD, id)?; + fn fetch_helper( + id: CommitId, + arc_current: &Arc< + Mutex>>, + >, + ) -> Result<()> { + let res = sync::get_commit_files(CWD, id)?; - log::trace!( - "get_commit_files: {} ({})", - id.to_string(), - res.len() - ); + log::trace!( + "get_commit_files: {} ({})", + id.to_string(), + res.len() + ); - { - let mut current = arc_current.lock()?; - *current = Some(Request(id, res)); - } + { + let mut current = arc_current.lock()?; + *current = Some(Request(id, res)); + } - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/diff.rs b/asyncgit/src/diff.rs index 9214c50b..06c5e38f 100644 --- a/asyncgit/src/diff.rs +++ b/asyncgit/src/diff.rs @@ -1,195 +1,195 @@ use crate::{ - error::Result, - hash, - sync::{self, CommitId}, - AsyncGitNotification, FileDiff, CWD, + error::Result, + hash, + sync::{self, CommitId}, + AsyncGitNotification, FileDiff, CWD, }; use crossbeam_channel::Sender; use std::{ - hash::Hash, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, - }, + hash::Hash, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, }; /// #[derive(Hash, Clone, PartialEq)] pub enum DiffType { - /// diff in a given commit - Commit(CommitId), - /// diff against staged file - Stage, - /// diff against file in workdir - WorkDir, + /// diff in a given commit + Commit(CommitId), + /// diff against staged file + Stage, + /// diff against file in workdir + WorkDir, } /// #[derive(Hash, Clone, PartialEq)] pub struct DiffParams { - /// path to the file to diff - pub path: String, - /// what kind of diff - pub diff_type: DiffType, + /// path to the file to diff + pub path: String, + /// what kind of diff + pub diff_type: DiffType, } struct Request(R, Option); #[derive(Default, Clone)] struct LastResult { - params: P, - hash: u64, - result: R, + params: P, + hash: u64, + result: R, } /// pub struct AsyncDiff { - current: Arc>>, - last: Arc>>>, - sender: Sender, - pending: Arc, + current: Arc>>, + last: Arc>>>, + sender: Sender, + pending: Arc, } impl AsyncDiff { - /// - pub fn new(sender: &Sender) -> Self { - Self { - current: Arc::new(Mutex::new(Request(0, None))), - last: Arc::new(Mutex::new(None)), - sender: sender.clone(), - pending: Arc::new(AtomicUsize::new(0)), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + current: Arc::new(Mutex::new(Request(0, None))), + last: Arc::new(Mutex::new(None)), + sender: sender.clone(), + pending: Arc::new(AtomicUsize::new(0)), + } + } - /// - pub fn last(&mut self) -> Result> { - let last = self.last.lock()?; + /// + pub fn last(&mut self) -> Result> { + let last = self.last.lock()?; - Ok(last.clone().map(|res| (res.params, res.result))) - } + Ok(last.clone().map(|res| (res.params, res.result))) + } - /// - pub fn refresh(&mut self) -> Result<()> { - if let Ok(Some(param)) = self.get_last_param() { - self.clear_current()?; - self.request(param)?; - } - Ok(()) - } + /// + pub fn refresh(&mut self) -> Result<()> { + if let Ok(Some(param)) = self.get_last_param() { + self.clear_current()?; + self.request(param)?; + } + Ok(()) + } - /// - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) > 0 - } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) > 0 + } - /// - pub fn request( - &mut self, - params: DiffParams, - ) -> Result> { - log::trace!("request"); + /// + pub fn request( + &mut self, + params: DiffParams, + ) -> Result> { + log::trace!("request"); - let hash = hash(¶ms); + let hash = hash(¶ms); - { - let mut current = self.current.lock()?; + { + let mut current = self.current.lock()?; - if current.0 == hash { - return Ok(current.1.clone()); - } + if current.0 == hash { + return Ok(current.1.clone()); + } - current.0 = hash; - current.1 = None; - } + current.0 = hash; + current.1 = None; + } - let arc_current = Arc::clone(&self.current); - let arc_last = Arc::clone(&self.last); - let sender = self.sender.clone(); - let arc_pending = Arc::clone(&self.pending); + let arc_current = Arc::clone(&self.current); + let arc_last = Arc::clone(&self.last); + let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); - self.pending.fetch_add(1, Ordering::Relaxed); + self.pending.fetch_add(1, Ordering::Relaxed); - rayon_core::spawn(move || { - let notify = Self::get_diff_helper( - params, - &arc_last, - &arc_current, - hash, - ); + rayon_core::spawn(move || { + let notify = Self::get_diff_helper( + params, + &arc_last, + &arc_current, + hash, + ); - let notify = match notify { - Err(err) => { - log::error!("get_diff_helper error: {}", err); - true - } - Ok(notify) => notify, - }; + let notify = match notify { + Err(err) => { + log::error!("get_diff_helper error: {}", err); + true + } + Ok(notify) => notify, + }; - arc_pending.fetch_sub(1, Ordering::Relaxed); + arc_pending.fetch_sub(1, Ordering::Relaxed); - sender - .send(if notify { - AsyncGitNotification::Diff - } else { - AsyncGitNotification::FinishUnchanged - }) - .expect("error sending diff"); - }); + sender + .send(if notify { + AsyncGitNotification::Diff + } else { + AsyncGitNotification::FinishUnchanged + }) + .expect("error sending diff"); + }); - Ok(None) - } + Ok(None) + } - fn get_diff_helper( - params: DiffParams, - arc_last: &Arc< - Mutex>>, - >, - arc_current: &Arc>>, - hash: u64, - ) -> Result { - let res = match params.diff_type { - DiffType::Stage => { - sync::diff::get_diff(CWD, ¶ms.path, true)? - } - DiffType::WorkDir => { - sync::diff::get_diff(CWD, ¶ms.path, false)? - } - DiffType::Commit(id) => sync::diff::get_diff_commit( - CWD, - id, - params.path.clone(), - )?, - }; + fn get_diff_helper( + params: DiffParams, + arc_last: &Arc< + Mutex>>, + >, + arc_current: &Arc>>, + hash: u64, + ) -> Result { + let res = match params.diff_type { + DiffType::Stage => { + sync::diff::get_diff(CWD, ¶ms.path, true)? + } + DiffType::WorkDir => { + sync::diff::get_diff(CWD, ¶ms.path, false)? + } + DiffType::Commit(id) => sync::diff::get_diff_commit( + CWD, + id, + params.path.clone(), + )?, + }; - let mut notify = false; - { - let mut current = arc_current.lock()?; - if current.0 == hash { - current.1 = Some(res.clone()); - notify = true; - } - } + let mut notify = false; + { + let mut current = arc_current.lock()?; + if current.0 == hash { + current.1 = Some(res.clone()); + notify = true; + } + } - { - let mut last = arc_last.lock()?; - *last = Some(LastResult { - result: res, - hash, - params, - }); - } + { + let mut last = arc_last.lock()?; + *last = Some(LastResult { + result: res, + hash, + params, + }); + } - Ok(notify) - } + Ok(notify) + } - fn get_last_param(&self) -> Result> { - Ok(self.last.lock()?.clone().map(|e| e.params)) - } + fn get_last_param(&self) -> Result> { + Ok(self.last.lock()?.clone().map(|e| e.params)) + } - fn clear_current(&mut self) -> Result<()> { - let mut current = self.current.lock()?; - current.0 = 0; - current.1 = None; - Ok(()) - } + fn clear_current(&mut self) -> Result<()> { + let mut current = self.current.lock()?; + current.0 = 0; + current.1 = None; + Ok(()) + } } diff --git a/asyncgit/src/error.rs b/asyncgit/src/error.rs index 80c490fe..d6e00d76 100644 --- a/asyncgit/src/error.rs +++ b/asyncgit/src/error.rs @@ -5,56 +5,56 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { - #[error("`{0}`")] - Generic(String), + #[error("`{0}`")] + Generic(String), - #[error("git: no head found")] - NoHead, + #[error("git: no head found")] + NoHead, - #[error("git: remote url not found")] - UnknownRemote, + #[error("git: remote url not found")] + UnknownRemote, - #[error("git: inconclusive remotes")] - NoDefaultRemoteFound, + #[error("git: inconclusive remotes")] + NoDefaultRemoteFound, - #[error("git: work dir error")] - NoWorkDir, + #[error("git: work dir error")] + NoWorkDir, - #[error("git: uncommitted changes")] - UncommittedChanges, + #[error("git: uncommitted changes")] + UncommittedChanges, - #[error("git: can\u{2019}t run blame on a binary file")] - NoBlameOnBinaryFile, + #[error("git: can\u{2019}t run blame on a binary file")] + NoBlameOnBinaryFile, - #[error("binary file")] - BinaryFile, + #[error("binary file")] + BinaryFile, - #[error("io error:{0}")] - Io(#[from] std::io::Error), + #[error("io error:{0}")] + Io(#[from] std::io::Error), - #[error("git error:{0}")] - Git(#[from] git2::Error), + #[error("git error:{0}")] + Git(#[from] git2::Error), - #[error("utf8 error:{0}")] - Utf8Conversion(#[from] FromUtf8Error), + #[error("utf8 error:{0}")] + Utf8Conversion(#[from] FromUtf8Error), - #[error("TryFromInt error:{0}")] - IntConversion(#[from] TryFromIntError), + #[error("TryFromInt error:{0}")] + IntConversion(#[from] TryFromIntError), - #[error("EasyCast error:{0}")] - EasyCast(#[from] easy_cast::Error), + #[error("EasyCast error:{0}")] + EasyCast(#[from] easy_cast::Error), } pub type Result = std::result::Result; impl From> for Error { - fn from(error: std::sync::PoisonError) -> Self { - Self::Generic(format!("poison error: {}", error)) - } + fn from(error: std::sync::PoisonError) -> Self { + Self::Generic(format!("poison error: {}", error)) + } } impl From> for Error { - fn from(error: crossbeam_channel::SendError) -> Self { - Self::Generic(format!("send error: {}", error)) - } + fn from(error: crossbeam_channel::SendError) -> Self { + Self::Generic(format!("send error: {}", error)) + } } diff --git a/asyncgit/src/fetch.rs b/asyncgit/src/fetch.rs index a0063e67..ed8caa6c 100644 --- a/asyncgit/src/fetch.rs +++ b/asyncgit/src/fetch.rs @@ -1,159 +1,159 @@ use crate::{ - error::{Error, Result}, - sync::{ - cred::BasicAuthCredential, - remotes::{fetch, push::ProgressNotification}, - }, - AsyncGitNotification, RemoteProgress, CWD, + error::{Error, Result}, + sync::{ + cred::BasicAuthCredential, + remotes::{fetch, push::ProgressNotification}, + }, + AsyncGitNotification, RemoteProgress, CWD, }; use crossbeam_channel::{unbounded, Sender}; use std::{ - sync::{Arc, Mutex}, - thread, + sync::{Arc, Mutex}, + thread, }; /// #[derive(Default, Clone, Debug)] pub struct FetchRequest { - /// - pub remote: String, - /// - pub branch: String, - /// - pub basic_credential: Option, + /// + pub remote: String, + /// + pub branch: String, + /// + pub basic_credential: Option, } #[derive(Default, Clone, Debug)] struct FetchState { - request: FetchRequest, + request: FetchRequest, } /// pub struct AsyncFetch { - state: Arc>>, - last_result: Arc>>, - progress: Arc>>, - sender: Sender, + state: Arc>>, + last_result: Arc>>, + progress: Arc>>, + sender: Sender, } impl AsyncFetch { - /// - pub fn new(sender: &Sender) -> Self { - Self { - state: Arc::new(Mutex::new(None)), - last_result: Arc::new(Mutex::new(None)), - progress: Arc::new(Mutex::new(None)), - sender: sender.clone(), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + state: Arc::new(Mutex::new(None)), + last_result: Arc::new(Mutex::new(None)), + progress: Arc::new(Mutex::new(None)), + sender: sender.clone(), + } + } - /// - pub fn is_pending(&self) -> Result { - let state = self.state.lock()?; - Ok(state.is_some()) - } + /// + pub fn is_pending(&self) -> Result { + let state = self.state.lock()?; + Ok(state.is_some()) + } - /// - pub fn last_result(&self) -> Result> { - let res = self.last_result.lock()?; - Ok(res.clone()) - } + /// + pub fn last_result(&self) -> Result> { + let res = self.last_result.lock()?; + Ok(res.clone()) + } - /// - pub fn progress(&self) -> Result> { - let res = self.progress.lock()?; - Ok(res.as_ref().map(|progress| progress.clone().into())) - } + /// + pub fn progress(&self) -> Result> { + let res = self.progress.lock()?; + Ok(res.as_ref().map(|progress| progress.clone().into())) + } - /// - pub fn request(&mut self, params: FetchRequest) -> Result<()> { - log::trace!("request"); + /// + pub fn request(&mut self, params: FetchRequest) -> Result<()> { + log::trace!("request"); - if self.is_pending()? { - return Ok(()); - } + if self.is_pending()? { + return Ok(()); + } - self.set_request(¶ms)?; - RemoteProgress::set_progress(&self.progress, None)?; + self.set_request(¶ms)?; + RemoteProgress::set_progress(&self.progress, None)?; - let arc_state = Arc::clone(&self.state); - let arc_res = Arc::clone(&self.last_result); - let arc_progress = Arc::clone(&self.progress); - let sender = self.sender.clone(); + let arc_state = Arc::clone(&self.state); + let arc_res = Arc::clone(&self.last_result); + let arc_progress = Arc::clone(&self.progress); + let sender = self.sender.clone(); - thread::spawn(move || { - let (progress_sender, receiver) = unbounded(); + thread::spawn(move || { + let (progress_sender, receiver) = unbounded(); - let handle = RemoteProgress::spawn_receiver_thread( - AsyncGitNotification::Fetch, - sender.clone(), - receiver, - arc_progress, - ); + let handle = RemoteProgress::spawn_receiver_thread( + AsyncGitNotification::Fetch, + sender.clone(), + receiver, + arc_progress, + ); - let res = fetch( - CWD, - ¶ms.branch, - params.basic_credential, - Some(progress_sender.clone()), - ); + let res = fetch( + CWD, + ¶ms.branch, + params.basic_credential, + Some(progress_sender.clone()), + ); - progress_sender - .send(ProgressNotification::Done) - .expect("closing send failed"); + progress_sender + .send(ProgressNotification::Done) + .expect("closing send failed"); - handle.join().expect("joining thread failed"); + handle.join().expect("joining thread failed"); - Self::set_result(&arc_res, res).expect("result error"); + Self::set_result(&arc_res, res).expect("result error"); - Self::clear_request(&arc_state).expect("clear error"); + Self::clear_request(&arc_state).expect("clear error"); - sender - .send(AsyncGitNotification::Fetch) - .expect("AsyncNotification error"); - }); + sender + .send(AsyncGitNotification::Fetch) + .expect("AsyncNotification error"); + }); - Ok(()) - } + Ok(()) + } - fn set_request(&self, params: &FetchRequest) -> Result<()> { - let mut state = self.state.lock()?; + fn set_request(&self, params: &FetchRequest) -> Result<()> { + let mut state = self.state.lock()?; - if state.is_some() { - return Err(Error::Generic("pending request".into())); - } + if state.is_some() { + return Err(Error::Generic("pending request".into())); + } - *state = Some(FetchState { - request: params.clone(), - }); + *state = Some(FetchState { + request: params.clone(), + }); - Ok(()) - } + Ok(()) + } - fn clear_request( - state: &Arc>>, - ) -> Result<()> { - let mut state = state.lock()?; + fn clear_request( + state: &Arc>>, + ) -> Result<()> { + let mut state = state.lock()?; - *state = None; + *state = None; - Ok(()) - } + Ok(()) + } - fn set_result( - arc_result: &Arc>>, - res: Result, - ) -> Result<()> { - let mut last_res = arc_result.lock()?; + fn set_result( + arc_result: &Arc>>, + res: Result, + ) -> Result<()> { + let mut last_res = arc_result.lock()?; - *last_res = match res { - Ok(bytes) => Some((bytes, String::new())), - Err(e) => { - log::error!("fetch error: {}", e); - Some((0, e.to_string())) - } - }; + *last_res = match res { + Ok(bytes) => Some((bytes, String::new())), + Err(e) => { + log::error!("fetch error: {}", e); + Some((0, e.to_string())) + } + }; - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/lib.rs b/asyncgit/src/lib.rs index 64b92778..c77e9548 100644 --- a/asyncgit/src/lib.rs +++ b/asyncgit/src/lib.rs @@ -2,11 +2,11 @@ #![forbid(missing_docs)] #![deny( - unused_imports, - unused_must_use, - dead_code, - unstable_name_collisions, - unused_assignments + unused_imports, + unused_must_use, + dead_code, + unstable_name_collisions, + unused_assignments )] #![deny(unstable_name_collisions)] #![deny(clippy::all, clippy::perf, clippy::nursery, clippy::pedantic)] @@ -40,49 +40,49 @@ pub mod sync; mod tags; pub use crate::{ - blame::{AsyncBlame, BlameParams}, - commit_files::AsyncCommitFiles, - diff::{AsyncDiff, DiffParams, DiffType}, - fetch::{AsyncFetch, FetchRequest}, - push::{AsyncPush, PushRequest}, - push_tags::{AsyncPushTags, PushTagsRequest}, - remote_progress::{RemoteProgress, RemoteProgressState}, - revlog::{AsyncLog, FetchStatus}, - status::{AsyncStatus, StatusParams}, - sync::{ - diff::{DiffLine, DiffLineType, FileDiff}, - status::{StatusItem, StatusItemType}, - }, - tags::AsyncTags, + blame::{AsyncBlame, BlameParams}, + commit_files::AsyncCommitFiles, + diff::{AsyncDiff, DiffParams, DiffType}, + fetch::{AsyncFetch, FetchRequest}, + push::{AsyncPush, PushRequest}, + push_tags::{AsyncPushTags, PushTagsRequest}, + remote_progress::{RemoteProgress, RemoteProgressState}, + revlog::{AsyncLog, FetchStatus}, + status::{AsyncStatus, StatusParams}, + sync::{ + diff::{DiffLine, DiffLineType, FileDiff}, + status::{StatusItem, StatusItemType}, + }, + tags::AsyncTags, }; use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, }; /// this type is used to communicate events back through the channel #[derive(Copy, Clone, Debug, PartialEq)] pub enum AsyncGitNotification { - /// this indicates that no new state was fetched but that a async process finished - FinishUnchanged, - /// - Status, - /// - Diff, - /// - Log, - /// - CommitFiles, - /// - Tags, - /// - Push, - /// - PushTags, - /// - Fetch, - /// - Blame, + /// this indicates that no new state was fetched but that a async process finished + FinishUnchanged, + /// + Status, + /// + Diff, + /// + Log, + /// + CommitFiles, + /// + Tags, + /// + Push, + /// + PushTags, + /// + Fetch, + /// + Blame, } /// current working directory `./` @@ -90,16 +90,16 @@ pub static CWD: &str = "./"; /// helper function to calculate the hash of an arbitrary type that implements the `Hash` trait pub fn hash(v: &T) -> u64 { - let mut hasher = DefaultHasher::new(); - v.hash(&mut hasher); - hasher.finish() + let mut hasher = DefaultHasher::new(); + v.hash(&mut hasher); + hasher.finish() } /// pub fn register_tracing_logging() -> bool { - git2::trace_set(git2::TraceLevel::Trace, git_trace) + git2::trace_set(git2::TraceLevel::Trace, git_trace) } fn git_trace(level: git2::TraceLevel, msg: &str) { - log::info!("[{:?}]: {}", level, msg); + log::info!("[{:?}]: {}", level, msg); } diff --git a/asyncgit/src/progress.rs b/asyncgit/src/progress.rs index 55ae92c0..3c58918c 100644 --- a/asyncgit/src/progress.rs +++ b/asyncgit/src/progress.rs @@ -6,49 +6,49 @@ use std::cmp; /// #[derive(Clone, Debug)] pub struct ProgressPercent { - /// percent 0..100 - pub progress: u8, + /// percent 0..100 + pub progress: u8, } impl ProgressPercent { - /// - pub fn new(current: usize, total: usize) -> Self { - let total = f64::conv(cmp::max(current, total)); - let progress = f64::conv(current) / total * 100.0; - let progress = u8::try_conv_nearest(progress).unwrap_or(100); - Self { progress } - } - /// - pub const fn empty() -> Self { - Self { progress: 0 } - } - /// - pub const fn full() -> Self { - Self { progress: 100 } - } + /// + pub fn new(current: usize, total: usize) -> Self { + let total = f64::conv(cmp::max(current, total)); + let progress = f64::conv(current) / total * 100.0; + let progress = u8::try_conv_nearest(progress).unwrap_or(100); + Self { progress } + } + /// + pub const fn empty() -> Self { + Self { progress: 0 } + } + /// + pub const fn full() -> Self { + Self { progress: 100 } + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_progress_zero_total() { - let prog = ProgressPercent::new(1, 0); + #[test] + fn test_progress_zero_total() { + let prog = ProgressPercent::new(1, 0); - assert_eq!(prog.progress, 100); - } + assert_eq!(prog.progress, 100); + } - #[test] - fn test_progress_zero_all() { - let prog = ProgressPercent::new(0, 0); - assert_eq!(prog.progress, 100); - } + #[test] + fn test_progress_zero_all() { + let prog = ProgressPercent::new(0, 0); + assert_eq!(prog.progress, 100); + } - #[test] - fn test_progress_rounding() { - let prog = ProgressPercent::new(2, 10); + #[test] + fn test_progress_rounding() { + let prog = ProgressPercent::new(2, 10); - assert_eq!(prog.progress, 20); - } + assert_eq!(prog.progress, 20); + } } diff --git a/asyncgit/src/push.rs b/asyncgit/src/push.rs index 9bcfcd43..9c4f14cb 100644 --- a/asyncgit/src/push.rs +++ b/asyncgit/src/push.rs @@ -1,166 +1,166 @@ use crate::{ - error::{Error, Result}, - sync::{ - cred::BasicAuthCredential, remotes::push::push, - remotes::push::ProgressNotification, - }, - AsyncGitNotification, RemoteProgress, CWD, + error::{Error, Result}, + sync::{ + cred::BasicAuthCredential, remotes::push::push, + remotes::push::ProgressNotification, + }, + AsyncGitNotification, RemoteProgress, CWD, }; use crossbeam_channel::{unbounded, Sender}; use std::{ - sync::{Arc, Mutex}, - thread, + sync::{Arc, Mutex}, + thread, }; /// #[derive(Default, Clone, Debug)] pub struct PushRequest { - /// - pub remote: String, - /// - pub branch: String, - /// - pub force: bool, - /// - pub delete: bool, - /// - pub basic_credential: Option, + /// + pub remote: String, + /// + pub branch: String, + /// + pub force: bool, + /// + pub delete: bool, + /// + pub basic_credential: Option, } #[derive(Default, Clone, Debug)] struct PushState { - request: PushRequest, + request: PushRequest, } /// pub struct AsyncPush { - state: Arc>>, - last_result: Arc>>, - progress: Arc>>, - sender: Sender, + state: Arc>>, + last_result: Arc>>, + progress: Arc>>, + sender: Sender, } impl AsyncPush { - /// - pub fn new(sender: &Sender) -> Self { - Self { - state: Arc::new(Mutex::new(None)), - last_result: Arc::new(Mutex::new(None)), - progress: Arc::new(Mutex::new(None)), - sender: sender.clone(), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + state: Arc::new(Mutex::new(None)), + last_result: Arc::new(Mutex::new(None)), + progress: Arc::new(Mutex::new(None)), + sender: sender.clone(), + } + } - /// - pub fn is_pending(&self) -> Result { - let state = self.state.lock()?; - Ok(state.is_some()) - } + /// + pub fn is_pending(&self) -> Result { + let state = self.state.lock()?; + Ok(state.is_some()) + } - /// - pub fn last_result(&self) -> Result> { - let res = self.last_result.lock()?; - Ok(res.clone()) - } + /// + pub fn last_result(&self) -> Result> { + let res = self.last_result.lock()?; + Ok(res.clone()) + } - /// - pub fn progress(&self) -> Result> { - let res = self.progress.lock()?; - Ok(res.as_ref().map(|progress| progress.clone().into())) - } + /// + pub fn progress(&self) -> Result> { + let res = self.progress.lock()?; + Ok(res.as_ref().map(|progress| progress.clone().into())) + } - /// - pub fn request(&mut self, params: PushRequest) -> Result<()> { - log::trace!("request"); + /// + pub fn request(&mut self, params: PushRequest) -> Result<()> { + log::trace!("request"); - if self.is_pending()? { - return Ok(()); - } + if self.is_pending()? { + return Ok(()); + } - self.set_request(¶ms)?; - RemoteProgress::set_progress(&self.progress, None)?; + self.set_request(¶ms)?; + RemoteProgress::set_progress(&self.progress, None)?; - let arc_state = Arc::clone(&self.state); - let arc_res = Arc::clone(&self.last_result); - let arc_progress = Arc::clone(&self.progress); - let sender = self.sender.clone(); + let arc_state = Arc::clone(&self.state); + let arc_res = Arc::clone(&self.last_result); + let arc_progress = Arc::clone(&self.progress); + let sender = self.sender.clone(); - thread::spawn(move || { - let (progress_sender, receiver) = unbounded(); + thread::spawn(move || { + let (progress_sender, receiver) = unbounded(); - let handle = RemoteProgress::spawn_receiver_thread( - AsyncGitNotification::Push, - sender.clone(), - receiver, - arc_progress, - ); + let handle = RemoteProgress::spawn_receiver_thread( + AsyncGitNotification::Push, + sender.clone(), + receiver, + arc_progress, + ); - let res = push( - CWD, - params.remote.as_str(), - params.branch.as_str(), - params.force, - params.delete, - params.basic_credential.clone(), - Some(progress_sender.clone()), - ); + let res = push( + CWD, + params.remote.as_str(), + params.branch.as_str(), + params.force, + params.delete, + params.basic_credential.clone(), + Some(progress_sender.clone()), + ); - progress_sender - .send(ProgressNotification::Done) - .expect("closing send failed"); + progress_sender + .send(ProgressNotification::Done) + .expect("closing send failed"); - handle.join().expect("joining thread failed"); + handle.join().expect("joining thread failed"); - Self::set_result(&arc_res, res).expect("result error"); + Self::set_result(&arc_res, res).expect("result error"); - Self::clear_request(&arc_state).expect("clear error"); + Self::clear_request(&arc_state).expect("clear error"); - sender - .send(AsyncGitNotification::Push) - .expect("error sending push"); - }); + sender + .send(AsyncGitNotification::Push) + .expect("error sending push"); + }); - Ok(()) - } + Ok(()) + } - fn set_request(&self, params: &PushRequest) -> Result<()> { - let mut state = self.state.lock()?; + fn set_request(&self, params: &PushRequest) -> Result<()> { + let mut state = self.state.lock()?; - if state.is_some() { - return Err(Error::Generic("pending request".into())); - } + if state.is_some() { + return Err(Error::Generic("pending request".into())); + } - *state = Some(PushState { - request: params.clone(), - }); + *state = Some(PushState { + request: params.clone(), + }); - Ok(()) - } + Ok(()) + } - fn clear_request( - state: &Arc>>, - ) -> Result<()> { - let mut state = state.lock()?; + fn clear_request( + state: &Arc>>, + ) -> Result<()> { + let mut state = state.lock()?; - *state = None; + *state = None; - Ok(()) - } + Ok(()) + } - fn set_result( - arc_result: &Arc>>, - res: Result<()>, - ) -> Result<()> { - let mut last_res = arc_result.lock()?; + fn set_result( + arc_result: &Arc>>, + res: Result<()>, + ) -> Result<()> { + let mut last_res = arc_result.lock()?; - *last_res = match res { - Ok(_) => None, - Err(e) => { - log::error!("push error: {}", e); - Some(e.to_string()) - } - }; + *last_res = match res { + Ok(_) => None, + Err(e) => { + log::error!("push error: {}", e); + Some(e.to_string()) + } + }; - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/push_tags.rs b/asyncgit/src/push_tags.rs index 4661917b..01fc1cff 100644 --- a/asyncgit/src/push_tags.rs +++ b/asyncgit/src/push_tags.rs @@ -1,153 +1,153 @@ use crate::{ - error::{Error, Result}, - sync::{ - cred::BasicAuthCredential, - remotes::tags::{push_tags, PushTagsProgress}, - }, - AsyncGitNotification, RemoteProgress, CWD, + error::{Error, Result}, + sync::{ + cred::BasicAuthCredential, + remotes::tags::{push_tags, PushTagsProgress}, + }, + AsyncGitNotification, RemoteProgress, CWD, }; use crossbeam_channel::{unbounded, Sender}; use std::{ - sync::{Arc, Mutex}, - thread, + sync::{Arc, Mutex}, + thread, }; /// #[derive(Default, Clone, Debug)] pub struct PushTagsRequest { - /// - pub remote: String, - /// - pub basic_credential: Option, + /// + pub remote: String, + /// + pub basic_credential: Option, } #[derive(Default, Clone, Debug)] struct PushState { - request: PushTagsRequest, + request: PushTagsRequest, } /// pub struct AsyncPushTags { - state: Arc>>, - last_result: Arc>>, - progress: Arc>>, - sender: Sender, + state: Arc>>, + last_result: Arc>>, + progress: Arc>>, + sender: Sender, } impl AsyncPushTags { - /// - pub fn new(sender: &Sender) -> Self { - Self { - state: Arc::new(Mutex::new(None)), - last_result: Arc::new(Mutex::new(None)), - progress: Arc::new(Mutex::new(None)), - sender: sender.clone(), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + state: Arc::new(Mutex::new(None)), + last_result: Arc::new(Mutex::new(None)), + progress: Arc::new(Mutex::new(None)), + sender: sender.clone(), + } + } - /// - pub fn is_pending(&self) -> Result { - let state = self.state.lock()?; - Ok(state.is_some()) - } + /// + pub fn is_pending(&self) -> Result { + let state = self.state.lock()?; + Ok(state.is_some()) + } - /// - pub fn last_result(&self) -> Result> { - let res = self.last_result.lock()?; - Ok(res.clone()) - } + /// + pub fn last_result(&self) -> Result> { + let res = self.last_result.lock()?; + Ok(res.clone()) + } - /// - pub fn progress(&self) -> Result> { - let res = self.progress.lock()?; - Ok(*res) - } + /// + pub fn progress(&self) -> Result> { + let res = self.progress.lock()?; + Ok(*res) + } - /// - pub fn request(&mut self, params: PushTagsRequest) -> Result<()> { - log::trace!("request"); + /// + pub fn request(&mut self, params: PushTagsRequest) -> Result<()> { + log::trace!("request"); - if self.is_pending()? { - return Ok(()); - } + if self.is_pending()? { + return Ok(()); + } - self.set_request(¶ms)?; - RemoteProgress::set_progress(&self.progress, None)?; + self.set_request(¶ms)?; + RemoteProgress::set_progress(&self.progress, None)?; - let arc_state = Arc::clone(&self.state); - let arc_res = Arc::clone(&self.last_result); - let arc_progress = Arc::clone(&self.progress); - let sender = self.sender.clone(); + let arc_state = Arc::clone(&self.state); + let arc_res = Arc::clone(&self.last_result); + let arc_progress = Arc::clone(&self.progress); + let sender = self.sender.clone(); - thread::spawn(move || { - let (progress_sender, receiver) = unbounded(); + thread::spawn(move || { + let (progress_sender, receiver) = unbounded(); - let handle = RemoteProgress::spawn_receiver_thread( - AsyncGitNotification::PushTags, - sender.clone(), - receiver, - arc_progress, - ); + let handle = RemoteProgress::spawn_receiver_thread( + AsyncGitNotification::PushTags, + sender.clone(), + receiver, + arc_progress, + ); - let res = push_tags( - CWD, - params.remote.as_str(), - params.basic_credential.clone(), - Some(progress_sender), - ); + let res = push_tags( + CWD, + params.remote.as_str(), + params.basic_credential.clone(), + Some(progress_sender), + ); - handle.join().expect("joining thread failed"); + handle.join().expect("joining thread failed"); - Self::set_result(&arc_res, res).expect("result error"); + Self::set_result(&arc_res, res).expect("result error"); - Self::clear_request(&arc_state).expect("clear error"); + Self::clear_request(&arc_state).expect("clear error"); - sender - .send(AsyncGitNotification::PushTags) - .expect("error sending push"); - }); + sender + .send(AsyncGitNotification::PushTags) + .expect("error sending push"); + }); - Ok(()) - } + Ok(()) + } - fn set_request(&self, params: &PushTagsRequest) -> Result<()> { - let mut state = self.state.lock()?; + fn set_request(&self, params: &PushTagsRequest) -> Result<()> { + let mut state = self.state.lock()?; - if state.is_some() { - return Err(Error::Generic("pending request".into())); - } + if state.is_some() { + return Err(Error::Generic("pending request".into())); + } - *state = Some(PushState { - request: params.clone(), - }); + *state = Some(PushState { + request: params.clone(), + }); - Ok(()) - } + Ok(()) + } - fn clear_request( - state: &Arc>>, - ) -> Result<()> { - let mut state = state.lock()?; + fn clear_request( + state: &Arc>>, + ) -> Result<()> { + let mut state = state.lock()?; - *state = None; + *state = None; - Ok(()) - } + Ok(()) + } - fn set_result( - arc_result: &Arc>>, - res: Result<()>, - ) -> Result<()> { - let mut last_res = arc_result.lock()?; + fn set_result( + arc_result: &Arc>>, + res: Result<()>, + ) -> Result<()> { + let mut last_res = arc_result.lock()?; - *last_res = match res { - Ok(_) => None, - Err(e) => { - log::error!("push error: {}", e); - Some(e.to_string()) - } - }; + *last_res = match res { + Ok(_) => None, + Err(e) => { + log::error!("push error: {}", e); + Some(e.to_string()) + } + }; - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/remote_progress.rs b/asyncgit/src/remote_progress.rs index 23cb9d34..89792ecf 100644 --- a/asyncgit/src/remote_progress.rs +++ b/asyncgit/src/remote_progress.rs @@ -1,151 +1,151 @@ //! use crate::{ - error::Result, - progress::ProgressPercent, - sync::remotes::push::{AsyncProgress, ProgressNotification}, - AsyncGitNotification, + error::Result, + progress::ProgressPercent, + sync::remotes::push::{AsyncProgress, ProgressNotification}, + AsyncGitNotification, }; use crossbeam_channel::{Receiver, Sender}; use git2::PackBuilderStage; use std::{ - sync::{Arc, Mutex}, - thread::{self, JoinHandle}, - time::Duration, + sync::{Arc, Mutex}, + thread::{self, JoinHandle}, + time::Duration, }; /// used for push/pull #[derive(Clone, Debug)] pub enum RemoteProgressState { - /// - PackingAddingObject, - /// - PackingDeltafiction, - /// - Pushing, - /// fetch progress - Transfer, - /// remote progress done - Done, + /// + PackingAddingObject, + /// + PackingDeltafiction, + /// + Pushing, + /// fetch progress + Transfer, + /// remote progress done + Done, } /// #[derive(Clone, Debug)] pub struct RemoteProgress { - /// - pub state: RemoteProgressState, - /// - pub progress: ProgressPercent, + /// + pub state: RemoteProgressState, + /// + pub progress: ProgressPercent, } impl RemoteProgress { - /// - pub fn new( - state: RemoteProgressState, - current: usize, - total: usize, - ) -> Self { - Self { - state, - progress: ProgressPercent::new(current, total), - } - } + /// + pub fn new( + state: RemoteProgressState, + current: usize, + total: usize, + ) -> Self { + Self { + state, + progress: ProgressPercent::new(current, total), + } + } - /// - pub const fn get_progress_percent(&self) -> u8 { - self.progress.progress - } + /// + pub const fn get_progress_percent(&self) -> u8 { + self.progress.progress + } - pub(crate) fn set_progress( - progress: &Arc>>, - state: Option, - ) -> Result<()> { - let mut progress = progress.lock()?; + pub(crate) fn set_progress( + progress: &Arc>>, + state: Option, + ) -> Result<()> { + let mut progress = progress.lock()?; - *progress = state; + *progress = state; - Ok(()) - } + Ok(()) + } - /// spawn thread to listen to progress notifcations coming in from blocking remote git method (fetch/push) - pub(crate) fn spawn_receiver_thread< - T: 'static + AsyncProgress, - >( - notification_type: AsyncGitNotification, - sender: Sender, - receiver: Receiver, - progress: Arc>>, - ) -> JoinHandle<()> { - thread::spawn(move || loop { - let incoming = receiver.recv(); - match incoming { - Ok(update) => { - Self::set_progress( - &progress, - Some(update.clone()), - ) - .expect("set progress failed"); - sender - .send(notification_type) - .expect("Notification error"); + /// spawn thread to listen to progress notifcations coming in from blocking remote git method (fetch/push) + pub(crate) fn spawn_receiver_thread< + T: 'static + AsyncProgress, + >( + notification_type: AsyncGitNotification, + sender: Sender, + receiver: Receiver, + progress: Arc>>, + ) -> JoinHandle<()> { + thread::spawn(move || loop { + let incoming = receiver.recv(); + match incoming { + Ok(update) => { + Self::set_progress( + &progress, + Some(update.clone()), + ) + .expect("set progress failed"); + sender + .send(notification_type) + .expect("Notification error"); - //NOTE: for better debugging - thread::sleep(Duration::from_millis(1)); + //NOTE: for better debugging + thread::sleep(Duration::from_millis(1)); - if update.is_done() { - break; - } - } - Err(e) => { - log::error!( - "remote progress receiver error: {}", - e - ); - break; - } - } - }) - } + if update.is_done() { + break; + } + } + Err(e) => { + log::error!( + "remote progress receiver error: {}", + e + ); + break; + } + } + }) + } } impl From for RemoteProgress { - fn from(progress: ProgressNotification) -> Self { - match progress { - ProgressNotification::Packing { - stage, - current, - total, - } => match stage { - PackBuilderStage::AddingObjects => Self::new( - RemoteProgressState::PackingAddingObject, - current, - total, - ), - PackBuilderStage::Deltafication => Self::new( - RemoteProgressState::PackingDeltafiction, - current, - total, - ), - }, - ProgressNotification::PushTransfer { - current, - total, - .. - } => Self::new( - RemoteProgressState::Pushing, - current, - total, - ), - ProgressNotification::Transfer { - objects, - total_objects, - .. - } => Self::new( - RemoteProgressState::Transfer, - objects, - total_objects, - ), - _ => Self::new(RemoteProgressState::Done, 1, 1), - } - } + fn from(progress: ProgressNotification) -> Self { + match progress { + ProgressNotification::Packing { + stage, + current, + total, + } => match stage { + PackBuilderStage::AddingObjects => Self::new( + RemoteProgressState::PackingAddingObject, + current, + total, + ), + PackBuilderStage::Deltafication => Self::new( + RemoteProgressState::PackingDeltafiction, + current, + total, + ), + }, + ProgressNotification::PushTransfer { + current, + total, + .. + } => Self::new( + RemoteProgressState::Pushing, + current, + total, + ), + ProgressNotification::Transfer { + objects, + total_objects, + .. + } => Self::new( + RemoteProgressState::Transfer, + objects, + total_objects, + ), + _ => Self::new(RemoteProgressState::Done, 1, 1), + } + } } diff --git a/asyncgit/src/remote_tags.rs b/asyncgit/src/remote_tags.rs index dd0193eb..524fea8a 100644 --- a/asyncgit/src/remote_tags.rs +++ b/asyncgit/src/remote_tags.rs @@ -1,74 +1,74 @@ //! use crate::{ - asyncjob::AsyncJob, - error::Result, - sync::cred::BasicAuthCredential, - sync::remotes::{get_default_remote, tags_missing_remote}, - CWD, + asyncjob::AsyncJob, + error::Result, + sync::cred::BasicAuthCredential, + sync::remotes::{get_default_remote, tags_missing_remote}, + CWD, }; use std::sync::{Arc, Mutex}; enum JobState { - Request(Option), - Response(Result>), + Request(Option), + Response(Result>), } /// #[derive(Clone, Default)] pub struct AsyncRemoteTagsJob { - state: Arc>>, + state: Arc>>, } /// impl AsyncRemoteTagsJob { - /// - pub fn new( - basic_credential: Option, - ) -> Self { - Self { - state: Arc::new(Mutex::new(Some(JobState::Request( - basic_credential, - )))), - } - } + /// + pub fn new( + basic_credential: Option, + ) -> Self { + Self { + state: Arc::new(Mutex::new(Some(JobState::Request( + basic_credential, + )))), + } + } - /// - pub fn result(&self) -> Option>> { - if let Ok(mut state) = self.state.lock() { - if let Some(state) = state.take() { - return match state { - JobState::Request(_) => None, - JobState::Response(result) => Some(result), - }; - } - } + /// + pub fn result(&self) -> Option>> { + if let Ok(mut state) = self.state.lock() { + if let Some(state) = state.take() { + return match state { + JobState::Request(_) => None, + JobState::Response(result) => Some(result), + }; + } + } - None - } + None + } } impl AsyncJob for AsyncRemoteTagsJob { - fn run(&mut self) { - if let Ok(mut state) = self.state.lock() { - *state = state.take().map(|state| match state { - JobState::Request(basic_credential) => { - let result = - get_default_remote(CWD).and_then(|remote| { - tags_missing_remote( - CWD, - &remote, - basic_credential, - ) - }); + fn run(&mut self) { + if let Ok(mut state) = self.state.lock() { + *state = state.take().map(|state| match state { + JobState::Request(basic_credential) => { + let result = + get_default_remote(CWD).and_then(|remote| { + tags_missing_remote( + CWD, + &remote, + basic_credential, + ) + }); - JobState::Response(result) - } - JobState::Response(result) => { - JobState::Response(result) - } - }); - } - } + JobState::Response(result) + } + JobState::Response(result) => { + JobState::Response(result) + } + }); + } + } } diff --git a/asyncgit/src/revlog.rs b/asyncgit/src/revlog.rs index aa4445b6..e746b236 100644 --- a/asyncgit/src/revlog.rs +++ b/asyncgit/src/revlog.rs @@ -1,38 +1,38 @@ use crate::{ - error::Result, - sync::{utils::repo, CommitId, LogWalker, LogWalkerFilter}, - AsyncGitNotification, CWD, + error::Result, + sync::{utils::repo, CommitId, LogWalker, LogWalkerFilter}, + AsyncGitNotification, CWD, }; use crossbeam_channel::Sender; use git2::Oid; use scopetime::scope_time; use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, - }, - thread, - time::Duration, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + thread, + time::Duration, }; /// #[derive(PartialEq)] pub enum FetchStatus { - /// previous fetch still running - Pending, - /// no change expected - NoChange, - /// new walk was started - Started, + /// previous fetch still running + Pending, + /// no change expected + NoChange, + /// new walk was started + Started, } /// pub struct AsyncLog { - current: Arc>>, - sender: Sender, - pending: Arc, - background: Arc, - filter: Option, + current: Arc>>, + sender: Sender, + pending: Arc, + background: Arc, + filter: Option, } static LIMIT_COUNT: usize = 3000; @@ -40,162 +40,162 @@ static SLEEP_FOREGROUND: Duration = Duration::from_millis(2); static SLEEP_BACKGROUND: Duration = Duration::from_millis(1000); impl AsyncLog { - /// - pub fn new( - sender: &Sender, - filter: Option, - ) -> Self { - Self { - current: Arc::new(Mutex::new(Vec::new())), - sender: sender.clone(), - pending: Arc::new(AtomicBool::new(false)), - background: Arc::new(AtomicBool::new(false)), - filter, - } - } + /// + pub fn new( + sender: &Sender, + filter: Option, + ) -> Self { + Self { + current: Arc::new(Mutex::new(Vec::new())), + sender: sender.clone(), + pending: Arc::new(AtomicBool::new(false)), + background: Arc::new(AtomicBool::new(false)), + filter, + } + } - /// - pub fn count(&mut self) -> Result { - Ok(self.current.lock()?.len()) - } + /// + pub fn count(&mut self) -> Result { + Ok(self.current.lock()?.len()) + } - /// - pub fn get_slice( - &self, - start_index: usize, - amount: usize, - ) -> Result> { - let list = self.current.lock()?; - let list_len = list.len(); - let min = start_index.min(list_len); - let max = min + amount; - let max = max.min(list_len); - Ok(list[min..max].to_vec()) - } + /// + pub fn get_slice( + &self, + start_index: usize, + amount: usize, + ) -> Result> { + let list = self.current.lock()?; + let list_len = list.len(); + let min = start_index.min(list_len); + let max = min + amount; + let max = max.min(list_len); + Ok(list[min..max].to_vec()) + } - /// - pub fn position(&self, id: CommitId) -> Result> { - let list = self.current.lock()?; - let position = list.iter().position(|&x| x == id); + /// + pub fn position(&self, id: CommitId) -> Result> { + let list = self.current.lock()?; + let position = list.iter().position(|&x| x == id); - Ok(position) - } + Ok(position) + } - /// - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) - } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) + } - /// - pub fn set_background(&mut self) { - self.background.store(true, Ordering::Relaxed); - } + /// + pub fn set_background(&mut self) { + self.background.store(true, Ordering::Relaxed); + } - /// - fn current_head(&self) -> Result { - Ok(self - .current - .lock()? - .first() - .map_or(Oid::zero().into(), |f| *f)) - } + /// + fn current_head(&self) -> Result { + Ok(self + .current + .lock()? + .first() + .map_or(Oid::zero().into(), |f| *f)) + } - /// - fn head_changed(&self) -> Result { - if let Ok(head) = repo(CWD)?.head() { - if let Some(head) = head.target() { - return Ok(head != self.current_head()?.into()); - } - } - Ok(false) - } + /// + fn head_changed(&self) -> Result { + if let Ok(head) = repo(CWD)?.head() { + if let Some(head) = head.target() { + return Ok(head != self.current_head()?.into()); + } + } + Ok(false) + } - /// - pub fn fetch(&mut self) -> Result { - self.background.store(false, Ordering::Relaxed); + /// + pub fn fetch(&mut self) -> Result { + self.background.store(false, Ordering::Relaxed); - if self.is_pending() { - return Ok(FetchStatus::Pending); - } + if self.is_pending() { + return Ok(FetchStatus::Pending); + } - if !self.head_changed()? { - return Ok(FetchStatus::NoChange); - } + if !self.head_changed()? { + return Ok(FetchStatus::NoChange); + } - self.clear()?; + self.clear()?; - let arc_current = Arc::clone(&self.current); - let sender = self.sender.clone(); - let arc_pending = Arc::clone(&self.pending); - let arc_background = Arc::clone(&self.background); + let arc_current = Arc::clone(&self.current); + let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); + let arc_background = Arc::clone(&self.background); - self.pending.store(true, Ordering::Relaxed); + self.pending.store(true, Ordering::Relaxed); - let filter = self.filter.clone(); + let filter = self.filter.clone(); - rayon_core::spawn(move || { - scope_time!("async::revlog"); + rayon_core::spawn(move || { + scope_time!("async::revlog"); - Self::fetch_helper( - &arc_current, - &arc_background, - &sender, - filter, - ) - .expect("failed to fetch"); + Self::fetch_helper( + &arc_current, + &arc_background, + &sender, + filter, + ) + .expect("failed to fetch"); - arc_pending.store(false, Ordering::Relaxed); + arc_pending.store(false, Ordering::Relaxed); - Self::notify(&sender); - }); + Self::notify(&sender); + }); - Ok(FetchStatus::Started) - } + Ok(FetchStatus::Started) + } - fn fetch_helper( - arc_current: &Arc>>, - arc_background: &Arc, - sender: &Sender, - filter: Option, - ) -> Result<()> { - let mut entries = Vec::with_capacity(LIMIT_COUNT); - let r = repo(CWD)?; - let mut walker = - LogWalker::new(&r, LIMIT_COUNT)?.filter(filter); - loop { - entries.clear(); - let res_is_err = walker.read(&mut entries).is_err(); + fn fetch_helper( + arc_current: &Arc>>, + arc_background: &Arc, + sender: &Sender, + filter: Option, + ) -> Result<()> { + let mut entries = Vec::with_capacity(LIMIT_COUNT); + let r = repo(CWD)?; + let mut walker = + LogWalker::new(&r, LIMIT_COUNT)?.filter(filter); + loop { + entries.clear(); + let res_is_err = walker.read(&mut entries).is_err(); - if !res_is_err { - let mut current = arc_current.lock()?; - current.extend(entries.iter()); - } + if !res_is_err { + let mut current = arc_current.lock()?; + current.extend(entries.iter()); + } - if res_is_err || entries.len() <= 1 { - break; - } - Self::notify(sender); + if res_is_err || entries.len() <= 1 { + break; + } + Self::notify(sender); - let sleep_duration = - if arc_background.load(Ordering::Relaxed) { - SLEEP_BACKGROUND - } else { - SLEEP_FOREGROUND - }; - thread::sleep(sleep_duration); - } + let sleep_duration = + if arc_background.load(Ordering::Relaxed) { + SLEEP_BACKGROUND + } else { + SLEEP_FOREGROUND + }; + thread::sleep(sleep_duration); + } - Ok(()) - } + Ok(()) + } - fn clear(&mut self) -> Result<()> { - self.current.lock()?.clear(); - Ok(()) - } + fn clear(&mut self) -> Result<()> { + self.current.lock()?.clear(); + Ok(()) + } - fn notify(sender: &Sender) { - sender - .send(AsyncGitNotification::Log) - .expect("error sending"); - } + fn notify(sender: &Sender) { + sender + .send(AsyncGitNotification::Log) + .expect("error sending"); + } } diff --git a/asyncgit/src/status.rs b/asyncgit/src/status.rs index 8533227a..7d76e25e 100644 --- a/asyncgit/src/status.rs +++ b/asyncgit/src/status.rs @@ -1,169 +1,169 @@ use crate::{ - error::Result, - hash, - sync::{self, status::StatusType}, - AsyncGitNotification, StatusItem, CWD, + error::Result, + hash, + sync::{self, status::StatusType}, + AsyncGitNotification, StatusItem, CWD, }; use crossbeam_channel::Sender; use std::{ - hash::Hash, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, - }, - time::{SystemTime, UNIX_EPOCH}, + hash::Hash, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + time::{SystemTime, UNIX_EPOCH}, }; fn current_tick() -> u128 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("time before unix epoch!") - .as_millis() + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time before unix epoch!") + .as_millis() } #[derive(Default, Hash, Clone)] pub struct Status { - pub items: Vec, + pub items: Vec, } /// #[derive(Default, Hash, Copy, Clone, PartialEq)] pub struct StatusParams { - tick: u128, - status_type: StatusType, + tick: u128, + status_type: StatusType, } impl StatusParams { - /// - pub fn new(status_type: StatusType) -> Self { - Self { - tick: current_tick(), - status_type, - } - } + /// + pub fn new(status_type: StatusType) -> Self { + Self { + tick: current_tick(), + status_type, + } + } } struct Request(R, Option); /// pub struct AsyncStatus { - current: Arc>>, - last: Arc>, - sender: Sender, - pending: Arc, + current: Arc>>, + last: Arc>, + sender: Sender, + pending: Arc, } impl AsyncStatus { - /// - pub fn new(sender: Sender) -> Self { - Self { - current: Arc::new(Mutex::new(Request(0, None))), - last: Arc::new(Mutex::new(Status::default())), - sender, - pending: Arc::new(AtomicUsize::new(0)), - } - } + /// + pub fn new(sender: Sender) -> Self { + Self { + current: Arc::new(Mutex::new(Request(0, None))), + last: Arc::new(Mutex::new(Status::default())), + sender, + pending: Arc::new(AtomicUsize::new(0)), + } + } - /// - pub fn last(&mut self) -> Result { - let last = self.last.lock()?; - Ok(last.clone()) - } + /// + pub fn last(&mut self) -> Result { + let last = self.last.lock()?; + Ok(last.clone()) + } - /// - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) > 0 - } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) > 0 + } - /// - pub fn fetch( - &mut self, - params: &StatusParams, - ) -> Result> { - if self.is_pending() { - log::trace!("request blocked, still pending"); - return Ok(None); - } + /// + pub fn fetch( + &mut self, + params: &StatusParams, + ) -> Result> { + if self.is_pending() { + log::trace!("request blocked, still pending"); + return Ok(None); + } - let hash_request = hash(¶ms); + let hash_request = hash(¶ms); - log::trace!( - "request: [hash: {}] (type: {:?})", - hash_request, - params.status_type, - ); + log::trace!( + "request: [hash: {}] (type: {:?})", + hash_request, + params.status_type, + ); - { - let mut current = self.current.lock()?; + { + let mut current = self.current.lock()?; - if current.0 == hash_request { - return Ok(current.1.clone()); - } + if current.0 == hash_request { + return Ok(current.1.clone()); + } - current.0 = hash_request; - current.1 = None; - } + current.0 = hash_request; + current.1 = None; + } - let arc_current = Arc::clone(&self.current); - let arc_last = Arc::clone(&self.last); - let sender = self.sender.clone(); - let arc_pending = Arc::clone(&self.pending); - let status_type = params.status_type; + let arc_current = Arc::clone(&self.current); + let arc_last = Arc::clone(&self.last); + let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); + let status_type = params.status_type; - self.pending.fetch_add(1, Ordering::Relaxed); + self.pending.fetch_add(1, Ordering::Relaxed); - rayon_core::spawn(move || { - let ok = Self::fetch_helper( - status_type, - hash_request, - &arc_current, - &arc_last, - ) - .is_ok(); + rayon_core::spawn(move || { + let ok = Self::fetch_helper( + status_type, + hash_request, + &arc_current, + &arc_last, + ) + .is_ok(); - arc_pending.fetch_sub(1, Ordering::Relaxed); + arc_pending.fetch_sub(1, Ordering::Relaxed); - if ok { - sender - .send(AsyncGitNotification::Status) - .expect("error sending status"); - } - }); + if ok { + sender + .send(AsyncGitNotification::Status) + .expect("error sending status"); + } + }); - Ok(None) - } + Ok(None) + } - fn fetch_helper( - status_type: StatusType, - hash_request: u64, - arc_current: &Arc>>, - arc_last: &Arc>, - ) -> Result<()> { - let res = Self::get_status(status_type)?; - log::trace!( - "status fetched: {} (type: {:?})", - hash_request, - status_type, - ); + fn fetch_helper( + status_type: StatusType, + hash_request: u64, + arc_current: &Arc>>, + arc_last: &Arc>, + ) -> Result<()> { + let res = Self::get_status(status_type)?; + log::trace!( + "status fetched: {} (type: {:?})", + hash_request, + status_type, + ); - { - let mut current = arc_current.lock()?; - if current.0 == hash_request { - current.1 = Some(res.clone()); - } - } + { + let mut current = arc_current.lock()?; + if current.0 == hash_request { + current.1 = Some(res.clone()); + } + } - { - let mut last = arc_last.lock()?; - *last = res; - } + { + let mut last = arc_last.lock()?; + *last = res; + } - Ok(()) - } + Ok(()) + } - fn get_status(status_type: StatusType) -> Result { - Ok(Status { - items: sync::status::get_status(CWD, status_type)?, - }) - } + fn get_status(status_type: StatusType) -> Result { + Ok(Status { + items: sync::status::get_status(CWD, status_type)?, + }) + } } diff --git a/asyncgit/src/sync/blame.rs b/asyncgit/src/sync/blame.rs index 4b5b0288..8a3aaed2 100644 --- a/asyncgit/src/sync/blame.rs +++ b/asyncgit/src/sync/blame.rs @@ -2,8 +2,8 @@ use super::{utils, CommitId}; use crate::{ - error::{Error, Result}, - sync::get_commits_info, + error::{Error, Result}, + sync::get_commits_info, }; use scopetime::scope_time; use std::collections::{HashMap, HashSet}; @@ -13,205 +13,205 @@ use std::path::Path; /// A `BlameHunk` contains all the information that will be shown to the user. #[derive(Clone, Hash, Debug, PartialEq, Eq)] pub struct BlameHunk { - /// - pub commit_id: CommitId, - /// - pub author: String, - /// - pub time: i64, - /// `git2::BlameHunk::final_start_line` returns 1-based indices, but - /// `start_line` is 0-based because the `Vec` storing the lines starts at - /// index 0. - pub start_line: usize, - /// - pub end_line: usize, + /// + pub commit_id: CommitId, + /// + pub author: String, + /// + pub time: i64, + /// `git2::BlameHunk::final_start_line` returns 1-based indices, but + /// `start_line` is 0-based because the `Vec` storing the lines starts at + /// index 0. + pub start_line: usize, + /// + pub end_line: usize, } /// A `BlameFile` represents a collection of lines. This is targeted at how the /// data will be used by the UI. #[derive(Clone, Debug)] pub struct FileBlame { - /// - pub commit_id: CommitId, - /// - pub path: String, - /// - pub lines: Vec<(Option, String)>, + /// + pub commit_id: CommitId, + /// + pub path: String, + /// + pub lines: Vec<(Option, String)>, } /// pub fn blame_file( - repo_path: &str, - file_path: &str, + repo_path: &str, + file_path: &str, ) -> Result { - scope_time!("blame_file"); + scope_time!("blame_file"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let commit_id = utils::get_head_repo(&repo)?; + let commit_id = utils::get_head_repo(&repo)?; - let spec = format!("{}:{}", commit_id.to_string(), file_path); + let spec = format!("{}:{}", commit_id.to_string(), file_path); - let object = repo.revparse_single(&spec)?; - let blob = repo.find_blob(object.id())?; + let object = repo.revparse_single(&spec)?; + let blob = repo.find_blob(object.id())?; - if blob.is_binary() { - return Err(Error::NoBlameOnBinaryFile); - } + if blob.is_binary() { + return Err(Error::NoBlameOnBinaryFile); + } - let blame = repo.blame_file(Path::new(file_path), None)?; + let blame = repo.blame_file(Path::new(file_path), None)?; - let reader = BufReader::new(blob.content()); + let reader = BufReader::new(blob.content()); - let unique_commit_ids: HashSet<_> = blame - .iter() - .map(|hunk| CommitId::new(hunk.final_commit_id())) - .collect(); - let mut commit_ids = Vec::with_capacity(unique_commit_ids.len()); - commit_ids.extend(unique_commit_ids); + let unique_commit_ids: HashSet<_> = blame + .iter() + .map(|hunk| CommitId::new(hunk.final_commit_id())) + .collect(); + let mut commit_ids = Vec::with_capacity(unique_commit_ids.len()); + commit_ids.extend(unique_commit_ids); - let commit_infos = get_commits_info(repo_path, &commit_ids, 0)?; - let unique_commit_infos: HashMap<_, _> = commit_infos - .iter() - .map(|commit_info| (commit_info.id, commit_info)) - .collect(); + let commit_infos = get_commits_info(repo_path, &commit_ids, 0)?; + let unique_commit_infos: HashMap<_, _> = commit_infos + .iter() + .map(|commit_info| (commit_info.id, commit_info)) + .collect(); - let lines: Vec<(Option, String)> = reader - .lines() - .enumerate() - .map(|(i, line)| { - // Line indices in a `FileBlame` are 1-based. - let corresponding_hunk = blame.get_line(i + 1); + let lines: Vec<(Option, String)> = reader + .lines() + .enumerate() + .map(|(i, line)| { + // Line indices in a `FileBlame` are 1-based. + let corresponding_hunk = blame.get_line(i + 1); - if let Some(hunk) = corresponding_hunk { - let commit_id = CommitId::new(hunk.final_commit_id()); - // Line indices in a `BlameHunk` are 1-based. - let start_line = - hunk.final_start_line().saturating_sub(1); - let end_line = - start_line.saturating_add(hunk.lines_in_hunk()); + if let Some(hunk) = corresponding_hunk { + let commit_id = CommitId::new(hunk.final_commit_id()); + // Line indices in a `BlameHunk` are 1-based. + let start_line = + hunk.final_start_line().saturating_sub(1); + let end_line = + start_line.saturating_add(hunk.lines_in_hunk()); - if let Some(commit_info) = - unique_commit_infos.get(&commit_id) - { - let hunk = BlameHunk { - commit_id, - author: commit_info.author.clone(), - time: commit_info.time, - start_line, - end_line, - }; + if let Some(commit_info) = + unique_commit_infos.get(&commit_id) + { + let hunk = BlameHunk { + commit_id, + author: commit_info.author.clone(), + time: commit_info.time, + start_line, + end_line, + }; - return ( - Some(hunk), - line.unwrap_or_else(|_| "".into()), - ); - } - } + return ( + Some(hunk), + line.unwrap_or_else(|_| "".into()), + ); + } + } - (None, line.unwrap_or_else(|_| "".into())) - }) - .collect(); + (None, line.unwrap_or_else(|_| "".into())) + }) + .collect(); - let file_blame = FileBlame { - commit_id, - path: file_path.into(), - lines, - }; + let file_blame = FileBlame { + commit_id, + path: file_path.into(), + lines, + }; - Ok(file_blame) + Ok(file_blame) } #[cfg(test)] mod tests { - use super::*; - use crate::error::Result; - use crate::sync::{ - commit, stage_add_file, tests::repo_init_empty, - }; - use std::{ - fs::{File, OpenOptions}, - io::Write, - path::Path, - }; + use super::*; + use crate::error::Result; + use crate::sync::{ + commit, stage_add_file, tests::repo_init_empty, + }; + use std::{ + fs::{File, OpenOptions}, + io::Write, + path::Path, + }; - #[test] - fn test_blame() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_blame() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert!(matches!(blame_file(&repo_path, "foo"), Err(_))); + assert!(matches!(blame_file(&repo_path, "foo"), Err(_))); - File::create(&root.join(file_path))? - .write_all(b"line 1\n")?; + File::create(&root.join(file_path))? + .write_all(b"line 1\n")?; - stage_add_file(repo_path, file_path)?; - commit(repo_path, "first commit")?; + stage_add_file(repo_path, file_path)?; + commit(repo_path, "first commit")?; - let blame = blame_file(&repo_path, "foo")?; + let blame = blame_file(&repo_path, "foo")?; - assert!(matches!( - blame.lines.as_slice(), - [( - Some(BlameHunk { - author, - start_line: 0, - end_line: 1, - .. - }), - line - )] if author == "name" && line == "line 1" - )); + assert!(matches!( + blame.lines.as_slice(), + [( + Some(BlameHunk { + author, + start_line: 0, + end_line: 1, + .. + }), + line + )] if author == "name" && line == "line 1" + )); - let mut file = OpenOptions::new() - .append(true) - .open(&root.join(file_path))?; + let mut file = OpenOptions::new() + .append(true) + .open(&root.join(file_path))?; - file.write(b"line 2\n")?; + file.write(b"line 2\n")?; - stage_add_file(repo_path, file_path)?; - commit(repo_path, "second commit")?; + stage_add_file(repo_path, file_path)?; + commit(repo_path, "second commit")?; - let blame = blame_file(&repo_path, "foo")?; + let blame = blame_file(&repo_path, "foo")?; - assert!(matches!( - blame.lines.as_slice(), - [ - ( - Some(BlameHunk { - start_line: 0, - end_line: 1, - .. - }), - first_line - ), - ( - Some(BlameHunk { - author, - start_line: 1, - end_line: 2, - .. - }), - second_line - ) - ] if author == "name" && first_line == "line 1" && second_line == "line 2" - )); + assert!(matches!( + blame.lines.as_slice(), + [ + ( + Some(BlameHunk { + start_line: 0, + end_line: 1, + .. + }), + first_line + ), + ( + Some(BlameHunk { + author, + start_line: 1, + end_line: 2, + .. + }), + second_line + ) + ] if author == "name" && first_line == "line 1" && second_line == "line 2" + )); - file.write(b"line 3\n")?; + file.write(b"line 3\n")?; - let blame = blame_file(&repo_path, "foo")?; + let blame = blame_file(&repo_path, "foo")?; - assert_eq!(blame.lines.len(), 2); + assert_eq!(blame.lines.len(), 2); - stage_add_file(repo_path, file_path)?; - commit(repo_path, "third commit")?; + stage_add_file(repo_path, file_path)?; + commit(repo_path, "third commit")?; - let blame = blame_file(&repo_path, "foo")?; + let blame = blame_file(&repo_path, "foo")?; - assert_eq!(blame.lines.len(), 3); + assert_eq!(blame.lines.len(), 3); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/branch/merge_commit.rs b/asyncgit/src/sync/branch/merge_commit.rs index ee287192..fcabf915 100644 --- a/asyncgit/src/sync/branch/merge_commit.rs +++ b/asyncgit/src/sync/branch/merge_commit.rs @@ -2,8 +2,8 @@ use super::BranchType; use crate::{ - error::{Error, Result}, - sync::{merge_msg, utils, CommitId}, + error::{Error, Result}, + sync::{merge_msg, utils, CommitId}, }; use git2::Commit; use scopetime::scope_time; @@ -12,260 +12,260 @@ use scopetime::scope_time; /// if we did not create conflicts we create a merge commit and return the commit id. /// Otherwise we return `None` pub fn merge_upstream_commit( - repo_path: &str, - branch_name: &str, + repo_path: &str, + branch_name: &str, ) -> Result> { - scope_time!("merge_upstream_commit"); + scope_time!("merge_upstream_commit"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let branch = repo.find_branch(branch_name, BranchType::Local)?; - let upstream = branch.upstream()?; + let branch = repo.find_branch(branch_name, BranchType::Local)?; + let upstream = branch.upstream()?; - let upstream_commit = upstream.get().peel_to_commit()?; + let upstream_commit = upstream.get().peel_to_commit()?; - let annotated_upstream = repo - .reference_to_annotated_commit(&upstream.into_reference())?; + let annotated_upstream = repo + .reference_to_annotated_commit(&upstream.into_reference())?; - let (analysis, pref) = - repo.merge_analysis(&[&annotated_upstream])?; + let (analysis, pref) = + repo.merge_analysis(&[&annotated_upstream])?; - if !analysis.is_normal() { - return Err(Error::Generic( - "normal merge not possible".into(), - )); - } + if !analysis.is_normal() { + return Err(Error::Generic( + "normal merge not possible".into(), + )); + } - if analysis.is_fast_forward() && pref.is_fastforward_only() { - return Err(Error::Generic( - "ff merge would be possible".into(), - )); - } + if analysis.is_fast_forward() && pref.is_fastforward_only() { + return Err(Error::Generic( + "ff merge would be possible".into(), + )); + } - //TODO: support merge on unborn? - if analysis.is_unborn() { - return Err(Error::Generic("head is unborn".into())); - } + //TODO: support merge on unborn? + if analysis.is_unborn() { + return Err(Error::Generic("head is unborn".into())); + } - repo.merge(&[&annotated_upstream], None, None)?; + repo.merge(&[&annotated_upstream], None, None)?; - if !repo.index()?.has_conflicts() { - let msg = merge_msg(repo_path)?; + if !repo.index()?.has_conflicts() { + let msg = merge_msg(repo_path)?; - let commit_id = - commit_merge_with_head(&repo, &[upstream_commit], &msg)?; + let commit_id = + commit_merge_with_head(&repo, &[upstream_commit], &msg)?; - return Ok(Some(commit_id)); - } + return Ok(Some(commit_id)); + } - Ok(None) + Ok(None) } pub(crate) fn commit_merge_with_head( - repo: &git2::Repository, - commits: &[Commit], - msg: &str, + repo: &git2::Repository, + commits: &[Commit], + msg: &str, ) -> Result { - let signature = - crate::sync::commit::signature_allow_undefined_name(repo)?; - let mut index = repo.index()?; - let tree_id = index.write_tree()?; - let tree = repo.find_tree(tree_id)?; - let head_commit = repo.find_commit( - crate::sync::utils::get_head_repo(repo)?.into(), - )?; + let signature = + crate::sync::commit::signature_allow_undefined_name(repo)?; + let mut index = repo.index()?; + let tree_id = index.write_tree()?; + let tree = repo.find_tree(tree_id)?; + let head_commit = repo.find_commit( + crate::sync::utils::get_head_repo(repo)?.into(), + )?; - let mut parents = vec![&head_commit]; - parents.extend(commits); + let mut parents = vec![&head_commit]; + parents.extend(commits); - let commit_id = repo - .commit( - Some("HEAD"), - &signature, - &signature, - msg, - &tree, - parents.as_slice(), - )? - .into(); - repo.cleanup_state()?; - Ok(commit_id) + let commit_id = repo + .commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + parents.as_slice(), + )? + .into(); + repo.cleanup_state()?; + Ok(commit_id) } #[cfg(test)] mod test { - use git2::Time; + use git2::Time; - use super::*; - use crate::sync::{ - branch_compare_upstream, - remotes::{fetch, push::push}, - tests::{ - debug_cmd_print, get_commit_ids, repo_clone, - repo_init_bare, write_commit_file, write_commit_file_at, - }, - RepoState, - }; + use super::*; + use crate::sync::{ + branch_compare_upstream, + remotes::{fetch, push::push}, + tests::{ + debug_cmd_print, get_commit_ids, repo_clone, + repo_init_bare, write_commit_file, write_commit_file_at, + }, + RepoState, + }; - #[test] - fn test_merge_normal() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_merge_normal() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let (clone2_dir, clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); + let clone2_dir = clone2_dir.path().to_str().unwrap(); - // clone1 + // clone1 - let commit1 = write_commit_file_at( - &clone1, - "test.txt", - "test", - "commit1", - Time::new(1, 0), - ); + let commit1 = write_commit_file_at( + &clone1, + "test.txt", + "test", + "commit1", + Time::new(1, 0), + ); - push( - clone1_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + clone1_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - // clone2 + // clone2 - let commit2 = write_commit_file_at( - &clone2, - "test2.txt", - "test", - "commit2", - Time::new(2, 0), - ); + let commit2 = write_commit_file_at( + &clone2, + "test2.txt", + "test", + "commit2", + Time::new(2, 0), + ); - //push should fail since origin diverged - assert!(push( - clone2_dir, "origin", "master", false, false, None, None, - ) - .is_err()); + //push should fail since origin diverged + assert!(push( + clone2_dir, "origin", "master", false, false, None, None, + ) + .is_err()); - //lets fetch from origin - let bytes = fetch(clone2_dir, "master", None, None).unwrap(); - assert!(bytes > 0); + //lets fetch from origin + let bytes = fetch(clone2_dir, "master", None, None).unwrap(); + assert!(bytes > 0); - //we should be one commit behind - assert_eq!( - branch_compare_upstream(clone2_dir, "master") - .unwrap() - .behind, - 1 - ); + //we should be one commit behind + assert_eq!( + branch_compare_upstream(clone2_dir, "master") + .unwrap() + .behind, + 1 + ); - let merge_commit = - merge_upstream_commit(clone2_dir, "master") - .unwrap() - .unwrap(); + let merge_commit = + merge_upstream_commit(clone2_dir, "master") + .unwrap() + .unwrap(); - let state = crate::sync::repo_state(clone2_dir).unwrap(); - assert_eq!(state, RepoState::Clean); + let state = crate::sync::repo_state(clone2_dir).unwrap(); + assert_eq!(state, RepoState::Clean); - assert!(!clone2.head_detached().unwrap()); + assert!(!clone2.head_detached().unwrap()); - let commits = get_commit_ids(&clone2, 10); - assert_eq!(commits.len(), 3); - assert_eq!(commits[0], merge_commit); - assert_eq!(commits[1], commit2); - assert_eq!(commits[2], commit1); + let commits = get_commit_ids(&clone2, 10); + assert_eq!(commits.len(), 3); + assert_eq!(commits[0], merge_commit); + assert_eq!(commits[1], commit2); + assert_eq!(commits[2], commit1); - //verify commit msg - let details = - crate::sync::get_commit_details(clone2_dir, merge_commit) - .unwrap(); - assert_eq!( + //verify commit msg + let details = + crate::sync::get_commit_details(clone2_dir, merge_commit) + .unwrap(); + assert_eq!( details.message.unwrap().combine(), String::from("Merge remote-tracking branch 'refs/remotes/origin/master'") ); - } + } - #[test] - fn test_merge_normal_non_ff() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_merge_normal_non_ff() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let (clone2_dir, clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - // clone1 + // clone1 - write_commit_file( - &clone1, - "test.bin", - "test\nfooo", - "commit1", - ); + write_commit_file( + &clone1, + "test.bin", + "test\nfooo", + "commit1", + ); - debug_cmd_print( - clone2_dir.path().to_str().unwrap(), - "git status", - ); + debug_cmd_print( + clone2_dir.path().to_str().unwrap(), + "git status", + ); - push( - clone1_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + clone1_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - // clone2 + // clone2 - write_commit_file( - &clone2, - "test.bin", - "foobar\ntest", - "commit2", - ); + write_commit_file( + &clone2, + "test.bin", + "foobar\ntest", + "commit2", + ); - let bytes = fetch( - clone2_dir.path().to_str().unwrap(), - "master", - None, - None, - ) - .unwrap(); - assert!(bytes > 0); + let bytes = fetch( + clone2_dir.path().to_str().unwrap(), + "master", + None, + None, + ) + .unwrap(); + assert!(bytes > 0); - let res = merge_upstream_commit( - clone2_dir.path().to_str().unwrap(), - "master", - ) - .unwrap(); + let res = merge_upstream_commit( + clone2_dir.path().to_str().unwrap(), + "master", + ) + .unwrap(); - //this should not have commited cause we left conflicts behind - assert_eq!(res, None); + //this should not have commited cause we left conflicts behind + assert_eq!(res, None); - let state = crate::sync::repo_state( - clone2_dir.path().to_str().unwrap(), - ) - .unwrap(); + let state = crate::sync::repo_state( + clone2_dir.path().to_str().unwrap(), + ) + .unwrap(); - //validate the repo is in a merge state now - assert_eq!(state, RepoState::Merge); + //validate the repo is in a merge state now + assert_eq!(state, RepoState::Merge); - //check that we still only have the first commit - let commits = get_commit_ids(&clone1, 10); - assert_eq!(commits.len(), 1); - } + //check that we still only have the first commit + let commits = get_commit_ids(&clone1, 10); + assert_eq!(commits.len(), 1); + } } diff --git a/asyncgit/src/sync/branch/merge_ff.rs b/asyncgit/src/sync/branch/merge_ff.rs index 4ce6853f..f4e94d99 100644 --- a/asyncgit/src/sync/branch/merge_ff.rs +++ b/asyncgit/src/sync/branch/merge_ff.rs @@ -2,143 +2,143 @@ use super::BranchType; use crate::{ - error::{Error, Result}, - sync::utils, + error::{Error, Result}, + sync::utils, }; use scopetime::scope_time; /// pub fn branch_merge_upstream_fastforward( - repo_path: &str, - branch: &str, + repo_path: &str, + branch: &str, ) -> Result<()> { - scope_time!("branch_merge_upstream"); + scope_time!("branch_merge_upstream"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let branch = repo.find_branch(branch, BranchType::Local)?; - let upstream = branch.upstream()?; + let branch = repo.find_branch(branch, BranchType::Local)?; + let upstream = branch.upstream()?; - let upstream_commit = - upstream.into_reference().peel_to_commit()?; + let upstream_commit = + upstream.into_reference().peel_to_commit()?; - let annotated = - repo.find_annotated_commit(upstream_commit.id())?; + let annotated = + repo.find_annotated_commit(upstream_commit.id())?; - let (analysis, pref) = repo.merge_analysis(&[&annotated])?; + let (analysis, pref) = repo.merge_analysis(&[&annotated])?; - if !analysis.is_fast_forward() { - return Err(Error::Generic( - "fast forward merge not possible".into(), - )); - } + if !analysis.is_fast_forward() { + return Err(Error::Generic( + "fast forward merge not possible".into(), + )); + } - if pref.is_no_fast_forward() { - return Err(Error::Generic("fast forward not wanted".into())); - } + if pref.is_no_fast_forward() { + return Err(Error::Generic("fast forward not wanted".into())); + } - //TODO: support merge on unborn - if analysis.is_unborn() { - return Err(Error::Generic("head is unborn".into())); - } + //TODO: support merge on unborn + if analysis.is_unborn() { + return Err(Error::Generic("head is unborn".into())); + } - repo.checkout_tree(upstream_commit.as_object(), None)?; + repo.checkout_tree(upstream_commit.as_object(), None)?; - repo.head()?.set_target(annotated.id(), "")?; + repo.head()?.set_target(annotated.id(), "")?; - Ok(()) + Ok(()) } #[cfg(test)] pub mod test { - use super::*; - use crate::sync::{ - remotes::{fetch, push::push}, - tests::{ - debug_cmd_print, get_commit_ids, repo_clone, - repo_init_bare, write_commit_file, - }, - }; + use super::*; + use crate::sync::{ + remotes::{fetch, push::push}, + tests::{ + debug_cmd_print, get_commit_ids, repo_clone, + repo_init_bare, write_commit_file, + }, + }; - #[test] - fn test_merge_fastforward() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_merge_fastforward() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let (clone2_dir, clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - // clone1 + // clone1 - let commit1 = - write_commit_file(&clone1, "test.txt", "test", "commit1"); + let commit1 = + write_commit_file(&clone1, "test.txt", "test", "commit1"); - push( - clone1_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + clone1_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - // clone2 - debug_cmd_print( - clone2_dir.path().to_str().unwrap(), - "git pull --ff", - ); + // clone2 + debug_cmd_print( + clone2_dir.path().to_str().unwrap(), + "git pull --ff", + ); - let commit2 = write_commit_file( - &clone2, - "test2.txt", - "test", - "commit2", - ); + let commit2 = write_commit_file( + &clone2, + "test2.txt", + "test", + "commit2", + ); - push( - clone2_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + clone2_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - // clone1 again + // clone1 again - let bytes = fetch( - clone1_dir.path().to_str().unwrap(), - "master", - None, - None, - ) - .unwrap(); - assert!(bytes > 0); + let bytes = fetch( + clone1_dir.path().to_str().unwrap(), + "master", + None, + None, + ) + .unwrap(); + assert!(bytes > 0); - let bytes = fetch( - clone1_dir.path().to_str().unwrap(), - "master", - None, - None, - ) - .unwrap(); - assert_eq!(bytes, 0); + let bytes = fetch( + clone1_dir.path().to_str().unwrap(), + "master", + None, + None, + ) + .unwrap(); + assert_eq!(bytes, 0); - branch_merge_upstream_fastforward( - clone1_dir.path().to_str().unwrap(), - "master", - ) - .unwrap(); + branch_merge_upstream_fastforward( + clone1_dir.path().to_str().unwrap(), + "master", + ) + .unwrap(); - let commits = get_commit_ids(&clone1, 10); - assert_eq!(commits.len(), 2); - assert_eq!(commits[1], commit1); - assert_eq!(commits[0], commit2); - } + let commits = get_commit_ids(&clone1, 10); + assert_eq!(commits.len(), 2); + assert_eq!(commits[1], commit1); + assert_eq!(commits[0], commit2); + } } diff --git a/asyncgit/src/sync/branch/merge_rebase.rs b/asyncgit/src/sync/branch/merge_rebase.rs index e3d3a3dc..eb920094 100644 --- a/asyncgit/src/sync/branch/merge_rebase.rs +++ b/asyncgit/src/sync/branch/merge_rebase.rs @@ -1,341 +1,341 @@ //! merging from upstream (rebase) use crate::{ - error::{Error, Result}, - sync::utils, + error::{Error, Result}, + sync::utils, }; use git2::BranchType; use scopetime::scope_time; /// trys merging current branch with its upstrema using rebase pub fn merge_upstream_rebase( - repo_path: &str, - branch_name: &str, + repo_path: &str, + branch_name: &str, ) -> Result<()> { - scope_time!("merge_upstream_rebase"); + scope_time!("merge_upstream_rebase"); - let repo = utils::repo(repo_path)?; - if super::get_branch_name_repo(&repo)? != branch_name { - return Err(Error::Generic(String::from( - "can only rebase in head branch", - ))); - } + let repo = utils::repo(repo_path)?; + if super::get_branch_name_repo(&repo)? != branch_name { + return Err(Error::Generic(String::from( + "can only rebase in head branch", + ))); + } - let branch = repo.find_branch(branch_name, BranchType::Local)?; - let upstream = branch.upstream()?; - let upstream_commit = upstream.get().peel_to_commit()?; - let annotated_upstream = - repo.find_annotated_commit(upstream_commit.id())?; + let branch = repo.find_branch(branch_name, BranchType::Local)?; + let upstream = branch.upstream()?; + let upstream_commit = upstream.get().peel_to_commit()?; + let annotated_upstream = + repo.find_annotated_commit(upstream_commit.id())?; - let mut rebase = - repo.rebase(None, Some(&annotated_upstream), None, None)?; + let mut rebase = + repo.rebase(None, Some(&annotated_upstream), None, None)?; - let signature = - crate::sync::commit::signature_allow_undefined_name(&repo)?; + let signature = + crate::sync::commit::signature_allow_undefined_name(&repo)?; - while let Some(op) = rebase.next() { - let _op = op?; - // dbg!(op.id()); + while let Some(op) = rebase.next() { + let _op = op?; + // dbg!(op.id()); - if repo.index()?.has_conflicts() { - rebase.abort()?; - return Err(Error::Generic(String::from( - "conflicts while merging", - ))); - } + if repo.index()?.has_conflicts() { + rebase.abort()?; + return Err(Error::Generic(String::from( + "conflicts while merging", + ))); + } - rebase.commit(None, &signature, None)?; - } + rebase.commit(None, &signature, None)?; + } - if repo.index()?.has_conflicts() { - rebase.abort()?; - return Err(Error::Generic(String::from( - "conflicts while merging", - ))); - } + if repo.index()?.has_conflicts() { + rebase.abort()?; + return Err(Error::Generic(String::from( + "conflicts while merging", + ))); + } - rebase.finish(Some(&signature))?; + rebase.finish(Some(&signature))?; - Ok(()) + Ok(()) } #[cfg(test)] mod test { - use super::*; - use crate::sync::{ - branch_compare_upstream, get_commits_info, - remotes::{fetch, push::push}, - tests::{ - debug_cmd_print, get_commit_ids, repo_clone, - repo_init_bare, write_commit_file, write_commit_file_at, - }, - RepoState, - }; - use git2::{Repository, Time}; - - fn get_commit_msgs(r: &Repository) -> Vec { - let commits = get_commit_ids(r, 10); - get_commits_info( - r.workdir().unwrap().to_str().unwrap(), - &commits, - 10, - ) - .unwrap() - .into_iter() - .map(|c| c.message) - .collect() - } - - #[test] - fn test_merge_normal() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); - - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - - let clone1_dir = clone1_dir.path().to_str().unwrap(); - - // clone1 - - let _commit1 = write_commit_file_at( - &clone1, - "test.txt", - "test", - "commit1", - git2::Time::new(0, 0), - ); - - assert_eq!(clone1.head_detached().unwrap(), false); - - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); - - assert_eq!(clone1.head_detached().unwrap(), false); - - // clone2 - - let (clone2_dir, clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - - let clone2_dir = clone2_dir.path().to_str().unwrap(); - - let _commit2 = write_commit_file_at( - &clone2, - "test2.txt", - "test", - "commit2", - git2::Time::new(1, 0), - ); - - assert_eq!(clone2.head_detached().unwrap(), false); - - push( - clone2_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); - - assert_eq!(clone2.head_detached().unwrap(), false); - - // clone1 - - let _commit3 = write_commit_file_at( - &clone1, - "test3.txt", - "test", - "commit3", - git2::Time::new(2, 0), - ); - - assert_eq!(clone1.head_detached().unwrap(), false); - - //lets fetch from origin - let bytes = fetch(clone1_dir, "master", None, None).unwrap(); - assert!(bytes > 0); - - //we should be one commit behind - assert_eq!( - branch_compare_upstream(clone1_dir, "master") - .unwrap() - .behind, - 1 - ); - - // debug_cmd_print(clone1_dir, "git status"); - - assert_eq!(clone1.head_detached().unwrap(), false); - - merge_upstream_rebase(clone1_dir, "master").unwrap(); - - debug_cmd_print(clone1_dir, "git log"); - - let state = crate::sync::repo_state(clone1_dir).unwrap(); - assert_eq!(state, RepoState::Clean); - - let commits = get_commit_msgs(&clone1); - assert_eq!( - commits, - vec![ - String::from("commit3"), - String::from("commit2"), - String::from("commit1") - ] - ); + use super::*; + use crate::sync::{ + branch_compare_upstream, get_commits_info, + remotes::{fetch, push::push}, + tests::{ + debug_cmd_print, get_commit_ids, repo_clone, + repo_init_bare, write_commit_file, write_commit_file_at, + }, + RepoState, + }; + use git2::{Repository, Time}; + + fn get_commit_msgs(r: &Repository) -> Vec { + let commits = get_commit_ids(r, 10); + get_commits_info( + r.workdir().unwrap().to_str().unwrap(), + &commits, + 10, + ) + .unwrap() + .into_iter() + .map(|c| c.message) + .collect() + } + + #[test] + fn test_merge_normal() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); + + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + + let clone1_dir = clone1_dir.path().to_str().unwrap(); + + // clone1 + + let _commit1 = write_commit_file_at( + &clone1, + "test.txt", + "test", + "commit1", + git2::Time::new(0, 0), + ); + + assert_eq!(clone1.head_detached().unwrap(), false); + + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); + + assert_eq!(clone1.head_detached().unwrap(), false); + + // clone2 + + let (clone2_dir, clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + + let clone2_dir = clone2_dir.path().to_str().unwrap(); + + let _commit2 = write_commit_file_at( + &clone2, + "test2.txt", + "test", + "commit2", + git2::Time::new(1, 0), + ); + + assert_eq!(clone2.head_detached().unwrap(), false); + + push( + clone2_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); + + assert_eq!(clone2.head_detached().unwrap(), false); + + // clone1 + + let _commit3 = write_commit_file_at( + &clone1, + "test3.txt", + "test", + "commit3", + git2::Time::new(2, 0), + ); + + assert_eq!(clone1.head_detached().unwrap(), false); + + //lets fetch from origin + let bytes = fetch(clone1_dir, "master", None, None).unwrap(); + assert!(bytes > 0); + + //we should be one commit behind + assert_eq!( + branch_compare_upstream(clone1_dir, "master") + .unwrap() + .behind, + 1 + ); + + // debug_cmd_print(clone1_dir, "git status"); + + assert_eq!(clone1.head_detached().unwrap(), false); + + merge_upstream_rebase(clone1_dir, "master").unwrap(); + + debug_cmd_print(clone1_dir, "git log"); + + let state = crate::sync::repo_state(clone1_dir).unwrap(); + assert_eq!(state, RepoState::Clean); + + let commits = get_commit_msgs(&clone1); + assert_eq!( + commits, + vec![ + String::from("commit3"), + String::from("commit2"), + String::from("commit1") + ] + ); - assert_eq!(clone1.head_detached().unwrap(), false); - } + assert_eq!(clone1.head_detached().unwrap(), false); + } - #[test] - fn test_merge_multiple() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_merge_multiple() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - // clone1 + // clone1 - write_commit_file_at( - &clone1, - "test.txt", - "test", - "commit1", - Time::new(0, 0), - ); + write_commit_file_at( + &clone1, + "test.txt", + "test", + "commit1", + Time::new(0, 0), + ); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); - // clone2 + // clone2 - let (clone2_dir, clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); + let clone2_dir = clone2_dir.path().to_str().unwrap(); - write_commit_file_at( - &clone2, - "test2.txt", - "test", - "commit2", - Time::new(1, 0), - ); + write_commit_file_at( + &clone2, + "test2.txt", + "test", + "commit2", + Time::new(1, 0), + ); - push( - clone2_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); + push( + clone2_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); - // clone1 + // clone1 - write_commit_file_at( - &clone1, - "test3.txt", - "test", - "commit3", - Time::new(2, 0), - ); - write_commit_file_at( - &clone1, - "test4.txt", - "test", - "commit4", - Time::new(3, 0), - ); + write_commit_file_at( + &clone1, + "test3.txt", + "test", + "commit3", + Time::new(2, 0), + ); + write_commit_file_at( + &clone1, + "test4.txt", + "test", + "commit4", + Time::new(3, 0), + ); - //lets fetch from origin + //lets fetch from origin - fetch(clone1_dir, "master", None, None).unwrap(); + fetch(clone1_dir, "master", None, None).unwrap(); - merge_upstream_rebase(clone1_dir, "master").unwrap(); + merge_upstream_rebase(clone1_dir, "master").unwrap(); - debug_cmd_print(clone1_dir, "git log"); + debug_cmd_print(clone1_dir, "git log"); - let state = crate::sync::repo_state(clone1_dir).unwrap(); - assert_eq!(state, RepoState::Clean); + let state = crate::sync::repo_state(clone1_dir).unwrap(); + assert_eq!(state, RepoState::Clean); - let commits = get_commit_msgs(&clone1); - assert_eq!( - commits, - vec![ - String::from("commit4"), - String::from("commit3"), - String::from("commit2"), - String::from("commit1") - ] - ); + let commits = get_commit_msgs(&clone1); + assert_eq!( + commits, + vec![ + String::from("commit4"), + String::from("commit3"), + String::from("commit2"), + String::from("commit1") + ] + ); - assert_eq!(clone1.head_detached().unwrap(), false); - } + assert_eq!(clone1.head_detached().unwrap(), false); + } - #[test] - fn test_merge_conflict() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_merge_conflict() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - // clone1 + // clone1 - let _commit1 = - write_commit_file(&clone1, "test.txt", "test", "commit1"); + let _commit1 = + write_commit_file(&clone1, "test.txt", "test", "commit1"); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); - // clone2 + // clone2 - let (clone2_dir, clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); - - let _commit2 = write_commit_file( - &clone2, - "test2.txt", - "test", - "commit2", - ); + let clone2_dir = clone2_dir.path().to_str().unwrap(); + + let _commit2 = write_commit_file( + &clone2, + "test2.txt", + "test", + "commit2", + ); - push( - clone2_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); + push( + clone2_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); - // clone1 + // clone1 - let _commit3 = - write_commit_file(&clone1, "test2.txt", "foo", "commit3"); + let _commit3 = + write_commit_file(&clone1, "test2.txt", "foo", "commit3"); - let bytes = fetch(clone1_dir, "master", None, None).unwrap(); - assert!(bytes > 0); - - assert_eq!( - branch_compare_upstream(clone1_dir, "master") - .unwrap() - .behind, - 1 - ); + let bytes = fetch(clone1_dir, "master", None, None).unwrap(); + assert!(bytes > 0); + + assert_eq!( + branch_compare_upstream(clone1_dir, "master") + .unwrap() + .behind, + 1 + ); - let res = merge_upstream_rebase(clone1_dir, "master"); - assert!(res.is_err()); + let res = merge_upstream_rebase(clone1_dir, "master"); + assert!(res.is_err()); - let state = crate::sync::repo_state(clone1_dir).unwrap(); + let state = crate::sync::repo_state(clone1_dir).unwrap(); - assert_eq!(state, RepoState::Clean); + assert_eq!(state, RepoState::Clean); - let commits = get_commit_msgs(&clone1); - assert_eq!( - commits, - vec![String::from("commit3"), String::from("commit1")] - ); - } + let commits = get_commit_msgs(&clone1); + assert_eq!( + commits, + vec![String::from("commit3"), String::from("commit1")] + ); + } } diff --git a/asyncgit/src/sync/branch/mod.rs b/asyncgit/src/sync/branch/mod.rs index c3794c28..5b6bee6e 100644 --- a/asyncgit/src/sync/branch/mod.rs +++ b/asyncgit/src/sync/branch/mod.rs @@ -6,11 +6,11 @@ pub mod merge_rebase; pub mod rename; use super::{ - remotes::get_default_remote_in_repo, utils::bytes2string, + remotes::get_default_remote_in_repo, utils::bytes2string, }; use crate::{ - error::{Error, Result}, - sync::{utils, CommitId}, + error::{Error, Result}, + sync::{utils, CommitId}, }; use git2::{Branch, BranchType, Repository}; use scopetime::scope_time; @@ -19,741 +19,741 @@ use utils::get_head_repo; /// returns the branch-name head is currently pointing to /// this might be expensive, see `cached::BranchName` pub(crate) fn get_branch_name(repo_path: &str) -> Result { - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - get_branch_name_repo(&repo) + get_branch_name_repo(&repo) } /// ditto pub(crate) fn get_branch_name_repo( - repo: &Repository, + repo: &Repository, ) -> Result { - scope_time!("get_branch_name_repo"); + scope_time!("get_branch_name_repo"); - let iter = repo.branches(None)?; + let iter = repo.branches(None)?; - for b in iter { - let b = b?; + for b in iter { + let b = b?; - if b.0.is_head() { - let name = b.0.name()?.unwrap_or(""); - return Ok(name.into()); - } - } + if b.0.is_head() { + let name = b.0.name()?.unwrap_or(""); + return Ok(name.into()); + } + } - Err(Error::NoHead) + Err(Error::NoHead) } /// #[derive(Debug)] pub struct LocalBranch { - /// - pub is_head: bool, - /// - pub has_upstream: bool, - /// - pub remote: Option, + /// + pub is_head: bool, + /// + pub has_upstream: bool, + /// + pub remote: Option, } /// #[derive(Debug)] pub enum BranchDetails { - /// - Local(LocalBranch), - /// - Remote, + /// + Local(LocalBranch), + /// + Remote, } /// #[derive(Debug)] pub struct BranchInfo { - /// - pub name: String, - /// - pub reference: String, - /// - pub top_commit_message: String, - /// - pub top_commit: CommitId, - /// - pub details: BranchDetails, + /// + pub name: String, + /// + pub reference: String, + /// + pub top_commit_message: String, + /// + pub top_commit: CommitId, + /// + pub details: BranchDetails, } impl BranchInfo { - /// returns details about local branch or None - pub const fn local_details(&self) -> Option<&LocalBranch> { - if let BranchDetails::Local(details) = &self.details { - return Some(details); - } + /// returns details about local branch or None + pub const fn local_details(&self) -> Option<&LocalBranch> { + if let BranchDetails::Local(details) = &self.details { + return Some(details); + } - None - } + None + } } /// pub fn validate_branch_name(name: &str) -> Result { - scope_time!("validate_branch_name"); + scope_time!("validate_branch_name"); - let valid = Branch::name_is_valid(name)?; + let valid = Branch::name_is_valid(name)?; - Ok(valid) + Ok(valid) } /// returns a list of `BranchInfo` with a simple summary on each branch /// `local` filters for local branches otherwise remote branches will be returned pub fn get_branches_info( - repo_path: &str, - local: bool, + repo_path: &str, + local: bool, ) -> Result> { - scope_time!("get_branches_info"); + scope_time!("get_branches_info"); - let filter = if local { - BranchType::Local - } else { - BranchType::Remote - }; + let filter = if local { + BranchType::Local + } else { + BranchType::Remote + }; - let repo = utils::repo(repo_path)?; - let mut branches_for_display: Vec = repo - .branches(Some(filter))? - .map(|b| { - let branch = b?.0; - let top_commit = branch.get().peel_to_commit()?; - let reference = bytes2string(branch.get().name_bytes())?; - let upstream = branch.upstream(); + let repo = utils::repo(repo_path)?; + let mut branches_for_display: Vec = repo + .branches(Some(filter))? + .map(|b| { + let branch = b?.0; + let top_commit = branch.get().peel_to_commit()?; + let reference = bytes2string(branch.get().name_bytes())?; + let upstream = branch.upstream(); - let remote = repo - .branch_upstream_remote(&reference) - .ok() - .as_ref() - .and_then(git2::Buf::as_str) - .map(String::from); + let remote = repo + .branch_upstream_remote(&reference) + .ok() + .as_ref() + .and_then(git2::Buf::as_str) + .map(String::from); - let details = if local { - BranchDetails::Local(LocalBranch { - is_head: branch.is_head(), - has_upstream: upstream.is_ok(), - remote, - }) - } else { - BranchDetails::Remote - }; + let details = if local { + BranchDetails::Local(LocalBranch { + is_head: branch.is_head(), + has_upstream: upstream.is_ok(), + remote, + }) + } else { + BranchDetails::Remote + }; - Ok(BranchInfo { - name: bytes2string(branch.name_bytes()?)?, - reference, - top_commit_message: bytes2string( - top_commit.summary_bytes().unwrap_or_default(), - )?, - top_commit: top_commit.id().into(), - details, - }) - }) - .filter_map(Result::ok) - .collect(); + Ok(BranchInfo { + name: bytes2string(branch.name_bytes()?)?, + reference, + top_commit_message: bytes2string( + top_commit.summary_bytes().unwrap_or_default(), + )?, + top_commit: top_commit.id().into(), + details, + }) + }) + .filter_map(Result::ok) + .collect(); - branches_for_display.sort_by(|a, b| a.name.cmp(&b.name)); + branches_for_display.sort_by(|a, b| a.name.cmp(&b.name)); - Ok(branches_for_display) + Ok(branches_for_display) } /// #[derive(Debug, Default)] pub struct BranchCompare { - /// - pub ahead: usize, - /// - pub behind: usize, + /// + pub ahead: usize, + /// + pub behind: usize, } /// pub(crate) fn branch_set_upstream( - repo: &Repository, - branch_name: &str, + repo: &Repository, + branch_name: &str, ) -> Result<()> { - scope_time!("branch_set_upstream"); + scope_time!("branch_set_upstream"); - let mut branch = - repo.find_branch(branch_name, BranchType::Local)?; + let mut branch = + repo.find_branch(branch_name, BranchType::Local)?; - if branch.upstream().is_err() { - let remote = get_default_remote_in_repo(repo)?; - let upstream_name = format!("{}/{}", remote, branch_name); - branch.set_upstream(Some(upstream_name.as_str()))?; - } + if branch.upstream().is_err() { + let remote = get_default_remote_in_repo(repo)?; + let upstream_name = format!("{}/{}", remote, branch_name); + branch.set_upstream(Some(upstream_name.as_str()))?; + } - Ok(()) + Ok(()) } /// returns remote of the upstream tracking branch for `branch` pub fn get_branch_remote( - repo_path: &str, - branch: &str, + repo_path: &str, + branch: &str, ) -> Result> { - let repo = utils::repo(repo_path)?; - let branch = repo.find_branch(branch, BranchType::Local)?; - let reference = bytes2string(branch.get().name_bytes())?; - let remote_name = repo.branch_upstream_remote(&reference).ok(); - if let Some(remote_name) = remote_name { - Ok(Some(bytes2string(remote_name.as_ref())?)) - } else { - Ok(None) - } + let repo = utils::repo(repo_path)?; + let branch = repo.find_branch(branch, BranchType::Local)?; + let reference = bytes2string(branch.get().name_bytes())?; + let remote_name = repo.branch_upstream_remote(&reference).ok(); + if let Some(remote_name) = remote_name { + Ok(Some(bytes2string(remote_name.as_ref())?)) + } else { + Ok(None) + } } /// returns whether the pull merge strategy is set to rebase pub fn config_is_pull_rebase(repo_path: &str) -> Result { - let repo = utils::repo(repo_path)?; - let config = repo.config()?; + let repo = utils::repo(repo_path)?; + let config = repo.config()?; - if let Ok(rebase) = config.get_entry("pull.rebase") { - let value = - rebase.value().map(String::from).unwrap_or_default(); - return Ok(value == "true"); - }; + if let Ok(rebase) = config.get_entry("pull.rebase") { + let value = + rebase.value().map(String::from).unwrap_or_default(); + return Ok(value == "true"); + }; - Ok(false) + Ok(false) } /// pub fn branch_compare_upstream( - repo_path: &str, - branch: &str, + repo_path: &str, + branch: &str, ) -> Result { - scope_time!("branch_compare_upstream"); + scope_time!("branch_compare_upstream"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let branch = repo.find_branch(branch, BranchType::Local)?; + let branch = repo.find_branch(branch, BranchType::Local)?; - let upstream = branch.upstream()?; + let upstream = branch.upstream()?; - let branch_commit = - branch.into_reference().peel_to_commit()?.id(); + let branch_commit = + branch.into_reference().peel_to_commit()?.id(); - let upstream_commit = - upstream.into_reference().peel_to_commit()?.id(); + let upstream_commit = + upstream.into_reference().peel_to_commit()?.id(); - let (ahead, behind) = - repo.graph_ahead_behind(branch_commit, upstream_commit)?; + let (ahead, behind) = + repo.graph_ahead_behind(branch_commit, upstream_commit)?; - Ok(BranchCompare { ahead, behind }) + Ok(BranchCompare { ahead, behind }) } /// Modify HEAD to point to a branch then checkout head, does not work if there are uncommitted changes pub fn checkout_branch( - repo_path: &str, - branch_ref: &str, + repo_path: &str, + branch_ref: &str, ) -> Result<()> { - scope_time!("checkout_branch"); + scope_time!("checkout_branch"); - // This defaults to a safe checkout, so don't delete anything that - // hasn't been committed or stashed, in this case it will Err - let repo = utils::repo(repo_path)?; - let cur_ref = repo.head()?; - let statuses = repo.statuses(Some( - git2::StatusOptions::new().include_ignored(false), - ))?; + // This defaults to a safe checkout, so don't delete anything that + // hasn't been committed or stashed, in this case it will Err + let repo = utils::repo(repo_path)?; + let cur_ref = repo.head()?; + let statuses = repo.statuses(Some( + git2::StatusOptions::new().include_ignored(false), + ))?; - if statuses.is_empty() { - repo.set_head(branch_ref)?; + if statuses.is_empty() { + repo.set_head(branch_ref)?; - if let Err(e) = repo.checkout_head(Some( - git2::build::CheckoutBuilder::new().force(), - )) { - // This is safe beacuse cur_ref was just found - repo.set_head( - bytes2string(cur_ref.name_bytes())?.as_str(), - )?; - return Err(Error::Git(e)); - } - Ok(()) - } else { - Err(Error::UncommittedChanges) - } + if let Err(e) = repo.checkout_head(Some( + git2::build::CheckoutBuilder::new().force(), + )) { + // This is safe beacuse cur_ref was just found + repo.set_head( + bytes2string(cur_ref.name_bytes())?.as_str(), + )?; + return Err(Error::Git(e)); + } + Ok(()) + } else { + Err(Error::UncommittedChanges) + } } /// pub fn checkout_remote_branch( - repo_path: &str, - branch: &BranchInfo, + repo_path: &str, + branch: &BranchInfo, ) -> Result<()> { - scope_time!("checkout_remote_branch"); + scope_time!("checkout_remote_branch"); - let repo = utils::repo(repo_path)?; - let cur_ref = repo.head()?; + let repo = utils::repo(repo_path)?; + let cur_ref = repo.head()?; - if !repo - .statuses(Some( - git2::StatusOptions::new().include_ignored(false), - ))? - .is_empty() - { - return Err(Error::UncommittedChanges); - } + if !repo + .statuses(Some( + git2::StatusOptions::new().include_ignored(false), + ))? + .is_empty() + { + return Err(Error::UncommittedChanges); + } - let name = branch.name.rfind('/').map_or_else( - || branch.name.clone(), - |pos| branch.name[pos..].to_string(), - ); + let name = branch.name.rfind('/').map_or_else( + || branch.name.clone(), + |pos| branch.name[pos..].to_string(), + ); - let commit = repo.find_commit(branch.top_commit.into())?; - let mut new_branch = repo.branch(&name, &commit, false)?; - new_branch.set_upstream(Some(&branch.name))?; + let commit = repo.find_commit(branch.top_commit.into())?; + let mut new_branch = repo.branch(&name, &commit, false)?; + new_branch.set_upstream(Some(&branch.name))?; - repo.set_head( - bytes2string(new_branch.into_reference().name_bytes())? - .as_str(), - )?; + repo.set_head( + bytes2string(new_branch.into_reference().name_bytes())? + .as_str(), + )?; - if let Err(e) = repo.checkout_head(Some( - git2::build::CheckoutBuilder::new().force(), - )) { - // This is safe beacuse cur_ref was just found - repo.set_head(bytes2string(cur_ref.name_bytes())?.as_str())?; - return Err(Error::Git(e)); - } - Ok(()) + if let Err(e) = repo.checkout_head(Some( + git2::build::CheckoutBuilder::new().force(), + )) { + // This is safe beacuse cur_ref was just found + repo.set_head(bytes2string(cur_ref.name_bytes())?.as_str())?; + return Err(Error::Git(e)); + } + Ok(()) } /// The user must not be on the branch for the branch to be deleted pub fn delete_branch( - repo_path: &str, - branch_ref: &str, + repo_path: &str, + branch_ref: &str, ) -> Result<()> { - scope_time!("delete_branch"); + scope_time!("delete_branch"); - let repo = utils::repo(repo_path)?; - let branch_as_ref = repo.find_reference(branch_ref)?; - let mut branch = git2::Branch::wrap(branch_as_ref); - if branch.is_head() { - return Err(Error::Generic("You cannot be on the branch you want to delete, switch branch, then delete this branch".to_string())); - } - branch.delete()?; - Ok(()) + let repo = utils::repo(repo_path)?; + let branch_as_ref = repo.find_reference(branch_ref)?; + let mut branch = git2::Branch::wrap(branch_as_ref); + if branch.is_head() { + return Err(Error::Generic("You cannot be on the branch you want to delete, switch branch, then delete this branch".to_string())); + } + branch.delete()?; + Ok(()) } /// creates a new branch pointing to current HEAD commit and updating HEAD to new branch pub fn create_branch(repo_path: &str, name: &str) -> Result { - scope_time!("create_branch"); + scope_time!("create_branch"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let head_id = get_head_repo(&repo)?; - let head_commit = repo.find_commit(head_id.into())?; + let head_id = get_head_repo(&repo)?; + let head_commit = repo.find_commit(head_id.into())?; - let branch = repo.branch(name, &head_commit, false)?; - let branch_ref = branch.into_reference(); - let branch_ref_name = bytes2string(branch_ref.name_bytes())?; - repo.set_head(branch_ref_name.as_str())?; + let branch = repo.branch(name, &head_commit, false)?; + let branch_ref = branch.into_reference(); + let branch_ref_name = bytes2string(branch_ref.name_bytes())?; + repo.set_head(branch_ref_name.as_str())?; - Ok(branch_ref_name) + Ok(branch_ref_name) } #[cfg(test)] mod tests_branch_name { - use super::*; - use crate::sync::tests::{repo_init, repo_init_empty}; + use super::*; + use crate::sync::tests::{repo_init, repo_init_empty}; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!( - get_branch_name(repo_path).unwrap().as_str(), - "master" - ); - } + assert_eq!( + get_branch_name(repo_path).unwrap().as_str(), + "master" + ); + } - #[test] - fn test_empty_repo() { - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_empty_repo() { + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert!(matches!( - get_branch_name(repo_path), - Err(Error::NoHead) - )); - } + assert!(matches!( + get_branch_name(repo_path), + Err(Error::NoHead) + )); + } } #[cfg(test)] mod tests_create_branch { - use super::*; - use crate::sync::tests::repo_init; + use super::*; + use crate::sync::tests::repo_init; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - create_branch(repo_path, "branch1").unwrap(); + create_branch(repo_path, "branch1").unwrap(); - assert_eq!( - get_branch_name(repo_path).unwrap().as_str(), - "branch1" - ); - } + assert_eq!( + get_branch_name(repo_path).unwrap().as_str(), + "branch1" + ); + } } #[cfg(test)] mod tests_branch_compare { - use super::*; - use crate::sync::tests::repo_init; + use super::*; + use crate::sync::tests::repo_init; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - create_branch(repo_path, "test").unwrap(); + create_branch(repo_path, "test").unwrap(); - let res = branch_compare_upstream(repo_path, "test"); + let res = branch_compare_upstream(repo_path, "test"); - assert_eq!(res.is_err(), true); - } + assert_eq!(res.is_err(), true); + } } #[cfg(test)] mod tests_branches { - use super::*; - use crate::sync::{ - remotes::{get_remotes, push::push}, - rename_branch, - tests::{ - debug_cmd_print, repo_clone, repo_init, repo_init_bare, - write_commit_file, - }, - }; + use super::*; + use crate::sync::{ + remotes::{get_remotes, push::push}, + rename_branch, + tests::{ + debug_cmd_print, repo_clone, repo_init, repo_init_bare, + write_commit_file, + }, + }; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!( - get_branches_info(repo_path, true) - .unwrap() - .iter() - .map(|b| b.name.clone()) - .collect::>(), - vec!["master"] - ); - } + assert_eq!( + get_branches_info(repo_path, true) + .unwrap() + .iter() + .map(|b| b.name.clone()) + .collect::>(), + vec!["master"] + ); + } - #[test] - fn test_multiple() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_multiple() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - create_branch(repo_path, "test").unwrap(); + create_branch(repo_path, "test").unwrap(); - assert_eq!( - get_branches_info(repo_path, true) - .unwrap() - .iter() - .map(|b| b.name.clone()) - .collect::>(), - vec!["master", "test"] - ); - } + assert_eq!( + get_branches_info(repo_path, true) + .unwrap() + .iter() + .map(|b| b.name.clone()) + .collect::>(), + vec!["master", "test"] + ); + } - fn clone_branch_commit_push(target: &str, branch_name: &str) { - let (dir, repo) = repo_clone(target).unwrap(); - let dir = dir.path().to_str().unwrap(); + fn clone_branch_commit_push(target: &str, branch_name: &str) { + let (dir, repo) = repo_clone(target).unwrap(); + let dir = dir.path().to_str().unwrap(); - write_commit_file(&repo, "f1.txt", "foo", "c1"); - rename_branch(dir, "refs/heads/master", branch_name).unwrap(); - push(dir, "origin", branch_name, false, false, None, None) - .unwrap(); - } + write_commit_file(&repo, "f1.txt", "foo", "c1"); + rename_branch(dir, "refs/heads/master", branch_name).unwrap(); + push(dir, "origin", branch_name, false, false, None, None) + .unwrap(); + } - #[test] - fn test_remotes_of_branches() { - let (r1_path, _remote1) = repo_init_bare().unwrap(); - let (r2_path, _remote2) = repo_init_bare().unwrap(); - let (_r, repo) = repo_init().unwrap(); + #[test] + fn test_remotes_of_branches() { + let (r1_path, _remote1) = repo_init_bare().unwrap(); + let (r2_path, _remote2) = repo_init_bare().unwrap(); + let (_r, repo) = repo_init().unwrap(); - let r1_path = r1_path.path().to_str().unwrap(); - let r2_path = r2_path.path().to_str().unwrap(); + let r1_path = r1_path.path().to_str().unwrap(); + let r2_path = r2_path.path().to_str().unwrap(); - //Note: create those test branches in our remotes - clone_branch_commit_push(r1_path, "r1branch"); - clone_branch_commit_push(r2_path, "r2branch"); + //Note: create those test branches in our remotes + clone_branch_commit_push(r1_path, "r1branch"); + clone_branch_commit_push(r2_path, "r2branch"); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - //add the remotes - repo.remote("r1", r1_path).unwrap(); - repo.remote("r2", r2_path).unwrap(); + //add the remotes + repo.remote("r1", r1_path).unwrap(); + repo.remote("r2", r2_path).unwrap(); - //verify we got the remotes - let remotes = get_remotes(repo_path).unwrap(); - assert_eq!( - remotes, - vec![String::from("r1"), String::from("r2")] - ); + //verify we got the remotes + let remotes = get_remotes(repo_path).unwrap(); + assert_eq!( + remotes, + vec![String::from("r1"), String::from("r2")] + ); - //verify we got only master right now - let branches = get_branches_info(repo_path, true).unwrap(); - assert_eq!(branches.len(), 1); - assert_eq!(branches[0].name, String::from("master")); + //verify we got only master right now + let branches = get_branches_info(repo_path, true).unwrap(); + assert_eq!(branches.len(), 1); + assert_eq!(branches[0].name, String::from("master")); - //pull stuff from the two remotes - debug_cmd_print(repo_path, "git pull r1"); - debug_cmd_print(repo_path, "git pull r2"); + //pull stuff from the two remotes + debug_cmd_print(repo_path, "git pull r1"); + debug_cmd_print(repo_path, "git pull r2"); - //create local tracking branches - debug_cmd_print( - repo_path, - "git checkout --track r1/r1branch", - ); - debug_cmd_print( - repo_path, - "git checkout --track r2/r2branch", - ); + //create local tracking branches + debug_cmd_print( + repo_path, + "git checkout --track r1/r1branch", + ); + debug_cmd_print( + repo_path, + "git checkout --track r2/r2branch", + ); - let branches = get_branches_info(repo_path, true).unwrap(); - assert_eq!(branches.len(), 3); - assert_eq!( - branches[1] - .local_details() - .unwrap() - .remote - .as_ref() - .unwrap(), - "r1" - ); - assert_eq!( - branches[2] - .local_details() - .unwrap() - .remote - .as_ref() - .unwrap(), - "r2" - ); + let branches = get_branches_info(repo_path, true).unwrap(); + assert_eq!(branches.len(), 3); + assert_eq!( + branches[1] + .local_details() + .unwrap() + .remote + .as_ref() + .unwrap(), + "r1" + ); + assert_eq!( + branches[2] + .local_details() + .unwrap() + .remote + .as_ref() + .unwrap(), + "r2" + ); - assert_eq!( - get_branch_remote(repo_path, "r1branch") - .unwrap() - .unwrap(), - String::from("r1") - ); + assert_eq!( + get_branch_remote(repo_path, "r1branch") + .unwrap() + .unwrap(), + String::from("r1") + ); - assert_eq!( - get_branch_remote(repo_path, "r2branch") - .unwrap() - .unwrap(), - String::from("r2") - ); - } + assert_eq!( + get_branch_remote(repo_path, "r2branch") + .unwrap() + .unwrap(), + String::from("r2") + ); + } - #[test] - fn test_branch_remote_no_upstream() { - let (_r, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_branch_remote_no_upstream() { + let (_r, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!( - get_branch_remote(repo_path, "master").unwrap(), - None - ); - } + assert_eq!( + get_branch_remote(repo_path, "master").unwrap(), + None + ); + } - #[test] - fn test_branch_remote_no_branch() { - let (_r, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_branch_remote_no_branch() { + let (_r, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert!(get_branch_remote(repo_path, "foo").is_err()); - } + assert!(get_branch_remote(repo_path, "foo").is_err()); + } } #[cfg(test)] mod tests_checkout { - use super::*; - use crate::sync::tests::repo_init; + use super::*; + use crate::sync::tests::repo_init; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert!( - checkout_branch(repo_path, "refs/heads/master").is_ok() - ); - assert!( - checkout_branch(repo_path, "refs/heads/foobar").is_err() - ); - } + assert!( + checkout_branch(repo_path, "refs/heads/master").is_ok() + ); + assert!( + checkout_branch(repo_path, "refs/heads/foobar").is_err() + ); + } - #[test] - fn test_multiple() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_multiple() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - create_branch(repo_path, "test").unwrap(); + create_branch(repo_path, "test").unwrap(); - assert!(checkout_branch(repo_path, "refs/heads/test").is_ok()); - assert!( - checkout_branch(repo_path, "refs/heads/master").is_ok() - ); - assert!(checkout_branch(repo_path, "refs/heads/test").is_ok()); - } + assert!(checkout_branch(repo_path, "refs/heads/test").is_ok()); + assert!( + checkout_branch(repo_path, "refs/heads/master").is_ok() + ); + assert!(checkout_branch(repo_path, "refs/heads/test").is_ok()); + } } #[cfg(test)] mod test_delete_branch { - use super::*; - use crate::sync::tests::repo_init; + use super::*; + use crate::sync::tests::repo_init; - #[test] - fn test_delete_branch() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_delete_branch() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - create_branch(repo_path, "branch1").unwrap(); - create_branch(repo_path, "branch2").unwrap(); + create_branch(repo_path, "branch1").unwrap(); + create_branch(repo_path, "branch2").unwrap(); - checkout_branch(repo_path, "refs/heads/branch1").unwrap(); + checkout_branch(repo_path, "refs/heads/branch1").unwrap(); - assert_eq!( - repo.branches(None) - .unwrap() - .nth(1) - .unwrap() - .unwrap() - .0 - .name() - .unwrap() - .unwrap(), - "branch2" - ); + assert_eq!( + repo.branches(None) + .unwrap() + .nth(1) + .unwrap() + .unwrap() + .0 + .name() + .unwrap() + .unwrap(), + "branch2" + ); - delete_branch(repo_path, "refs/heads/branch2").unwrap(); + delete_branch(repo_path, "refs/heads/branch2").unwrap(); - assert_eq!( - repo.branches(None) - .unwrap() - .nth(1) - .unwrap() - .unwrap() - .0 - .name() - .unwrap() - .unwrap(), - "master" - ); - } + assert_eq!( + repo.branches(None) + .unwrap() + .nth(1) + .unwrap() + .unwrap() + .0 + .name() + .unwrap() + .unwrap(), + "master" + ); + } } #[cfg(test)] mod test_remote_branches { - use super::*; - use crate::sync::remotes::push::push; - use crate::sync::tests::{ - repo_clone, repo_init_bare, write_commit_file, - }; + use super::*; + use crate::sync::remotes::push::push; + use crate::sync::tests::{ + repo_clone, repo_init_bare, write_commit_file, + }; - #[test] - fn test_remote_branches() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_remote_branches() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - // clone1 + // clone1 - write_commit_file(&clone1, "test.txt", "test", "commit1"); + write_commit_file(&clone1, "test.txt", "test", "commit1"); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); - create_branch(clone1_dir, "foo").unwrap(); + create_branch(clone1_dir, "foo").unwrap(); - write_commit_file(&clone1, "test.txt", "test2", "commit2"); + write_commit_file(&clone1, "test.txt", "test2", "commit2"); - push(clone1_dir, "origin", "foo", false, false, None, None) - .unwrap(); + push(clone1_dir, "origin", "foo", false, false, None, None) + .unwrap(); - // clone2 + // clone2 - let (clone2_dir, _clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, _clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); + let clone2_dir = clone2_dir.path().to_str().unwrap(); - let local_branches = - get_branches_info(clone2_dir, true).unwrap(); + let local_branches = + get_branches_info(clone2_dir, true).unwrap(); - assert_eq!(local_branches.len(), 1); + assert_eq!(local_branches.len(), 1); - let branches = get_branches_info(clone2_dir, false).unwrap(); - assert_eq!(dbg!(&branches).len(), 3); - assert_eq!(&branches[0].name, "origin/HEAD"); - assert_eq!(&branches[1].name, "origin/foo"); - assert_eq!(&branches[2].name, "origin/master"); - } + let branches = get_branches_info(clone2_dir, false).unwrap(); + assert_eq!(dbg!(&branches).len(), 3); + assert_eq!(&branches[0].name, "origin/HEAD"); + assert_eq!(&branches[1].name, "origin/foo"); + assert_eq!(&branches[2].name, "origin/master"); + } - #[test] - fn test_checkout_remote_branch() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); + #[test] + fn test_checkout_remote_branch() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); - let (clone1_dir, clone1) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let (clone1_dir, clone1) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - // clone1 + // clone1 - write_commit_file(&clone1, "test.txt", "test", "commit1"); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); - create_branch(clone1_dir, "foo").unwrap(); - write_commit_file(&clone1, "test.txt", "test2", "commit2"); - push(clone1_dir, "origin", "foo", false, false, None, None) - .unwrap(); + write_commit_file(&clone1, "test.txt", "test", "commit1"); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); + create_branch(clone1_dir, "foo").unwrap(); + write_commit_file(&clone1, "test.txt", "test2", "commit2"); + push(clone1_dir, "origin", "foo", false, false, None, None) + .unwrap(); - // clone2 + // clone2 - let (clone2_dir, _clone2) = - repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); + let (clone2_dir, _clone2) = + repo_clone(r1_dir.path().to_str().unwrap()).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); + let clone2_dir = clone2_dir.path().to_str().unwrap(); - let local_branches = - get_branches_info(clone2_dir, true).unwrap(); + let local_branches = + get_branches_info(clone2_dir, true).unwrap(); - assert_eq!(local_branches.len(), 1); + assert_eq!(local_branches.len(), 1); - let branches = get_branches_info(clone2_dir, false).unwrap(); + let branches = get_branches_info(clone2_dir, false).unwrap(); - // checkout origin/foo - checkout_remote_branch(clone2_dir, &branches[1]).unwrap(); + // checkout origin/foo + checkout_remote_branch(clone2_dir, &branches[1]).unwrap(); - assert_eq!( - get_branches_info(clone2_dir, true).unwrap().len(), - 2 - ); + assert_eq!( + get_branches_info(clone2_dir, true).unwrap().len(), + 2 + ); - assert_eq!(&get_branch_name(clone2_dir).unwrap(), "foo"); - } + assert_eq!(&get_branch_name(clone2_dir).unwrap(), "foo"); + } } diff --git a/asyncgit/src/sync/branch/rename.rs b/asyncgit/src/sync/branch/rename.rs index 4bb369c5..f715fd7b 100644 --- a/asyncgit/src/sync/branch/rename.rs +++ b/asyncgit/src/sync/branch/rename.rs @@ -5,63 +5,63 @@ use scopetime::scope_time; /// Rename the branch reference pub fn rename_branch( - repo_path: &str, - branch_ref: &str, - new_name: &str, + repo_path: &str, + branch_ref: &str, + new_name: &str, ) -> Result<()> { - scope_time!("delete_branch"); + scope_time!("delete_branch"); - let repo = utils::repo(repo_path)?; - let branch_as_ref = repo.find_reference(branch_ref)?; - let mut branch = git2::Branch::wrap(branch_as_ref); - branch.rename(new_name, true)?; + let repo = utils::repo(repo_path)?; + let branch_as_ref = repo.find_reference(branch_ref)?; + let mut branch = git2::Branch::wrap(branch_as_ref); + branch.rename(new_name, true)?; - Ok(()) + Ok(()) } #[cfg(test)] mod test { - use super::super::*; - use super::rename_branch; - use crate::sync::tests::repo_init; + use super::super::*; + use super::rename_branch; + use crate::sync::tests::repo_init; - #[test] - fn test_rename_branch() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_rename_branch() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - create_branch(repo_path, "branch1").unwrap(); + create_branch(repo_path, "branch1").unwrap(); - checkout_branch(repo_path, "refs/heads/branch1").unwrap(); + checkout_branch(repo_path, "refs/heads/branch1").unwrap(); - assert_eq!( - repo.branches(None) - .unwrap() - .nth(0) - .unwrap() - .unwrap() - .0 - .name() - .unwrap() - .unwrap(), - "branch1" - ); + assert_eq!( + repo.branches(None) + .unwrap() + .nth(0) + .unwrap() + .unwrap() + .0 + .name() + .unwrap() + .unwrap(), + "branch1" + ); - rename_branch(repo_path, "refs/heads/branch1", "AnotherName") - .unwrap(); + rename_branch(repo_path, "refs/heads/branch1", "AnotherName") + .unwrap(); - assert_eq!( - repo.branches(None) - .unwrap() - .nth(0) - .unwrap() - .unwrap() - .0 - .name() - .unwrap() - .unwrap(), - "AnotherName" - ); - } + assert_eq!( + repo.branches(None) + .unwrap() + .nth(0) + .unwrap() + .unwrap() + .0 + .name() + .unwrap() + .unwrap(), + "AnotherName" + ); + } } diff --git a/asyncgit/src/sync/commit.rs b/asyncgit/src/sync/commit.rs index 6b0c4fba..0d5f7847 100644 --- a/asyncgit/src/sync/commit.rs +++ b/asyncgit/src/sync/commit.rs @@ -5,29 +5,29 @@ use scopetime::scope_time; /// pub fn amend( - repo_path: &str, - id: CommitId, - msg: &str, + repo_path: &str, + id: CommitId, + msg: &str, ) -> Result { - scope_time!("amend"); + scope_time!("amend"); - let repo = repo(repo_path)?; - let commit = repo.find_commit(id.into())?; + let repo = repo(repo_path)?; + let commit = repo.find_commit(id.into())?; - let mut index = repo.index()?; - let tree_id = index.write_tree()?; - let tree = repo.find_tree(tree_id)?; + let mut index = repo.index()?; + let tree_id = index.write_tree()?; + let tree = repo.find_tree(tree_id)?; - let new_id = commit.amend( - Some("HEAD"), - None, - None, - None, - Some(msg), - Some(&tree), - )?; + let new_id = commit.amend( + Some("HEAD"), + None, + None, + None, + Some(msg), + Some(&tree), + )?; - Ok(CommitId::new(new_id)) + Ok(CommitId::new(new_id)) } /// Wrap `Repository::signature` to allow unknown user.name. @@ -35,57 +35,57 @@ pub fn amend( /// See . #[allow(clippy::redundant_pub_crate)] pub(crate) fn signature_allow_undefined_name( - repo: &Repository, + repo: &Repository, ) -> std::result::Result, git2::Error> { - let signature = repo.signature(); + let signature = repo.signature(); - if let Err(ref e) = signature { - if e.code() == ErrorCode::NotFound { - let config = repo.config()?; + if let Err(ref e) = signature { + if e.code() == ErrorCode::NotFound { + let config = repo.config()?; - if let (Err(_), Ok(email_entry)) = ( - config.get_entry("user.name"), - config.get_entry("user.email"), - ) { - if let Some(email) = email_entry.value() { - return Signature::now("unknown", email); - } - }; - } - } + if let (Err(_), Ok(email_entry)) = ( + config.get_entry("user.name"), + config.get_entry("user.email"), + ) { + if let Some(email) = email_entry.value() { + return Signature::now("unknown", email); + } + }; + } + } - signature + signature } /// this does not run any git hooks pub fn commit(repo_path: &str, msg: &str) -> Result { - scope_time!("commit"); + scope_time!("commit"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let signature = signature_allow_undefined_name(&repo)?; - let mut index = repo.index()?; - let tree_id = index.write_tree()?; - let tree = repo.find_tree(tree_id)?; + let signature = signature_allow_undefined_name(&repo)?; + let mut index = repo.index()?; + let tree_id = index.write_tree()?; + let tree = repo.find_tree(tree_id)?; - let parents = if let Ok(id) = get_head_repo(&repo) { - vec![repo.find_commit(id.into())?] - } else { - Vec::new() - }; + let parents = if let Ok(id) = get_head_repo(&repo) { + vec![repo.find_commit(id.into())?] + } else { + Vec::new() + }; - let parents = parents.iter().collect::>(); + let parents = parents.iter().collect::>(); - Ok(repo - .commit( - Some("HEAD"), - &signature, - &signature, - msg, - &tree, - parents.as_slice(), - )? - .into()) + Ok(repo + .commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + parents.as_slice(), + )? + .into()) } /// Tag a commit. @@ -93,246 +93,246 @@ pub fn commit(repo_path: &str, msg: &str) -> Result { /// This function will return an `Err(…)` variant if the tag’s name is refused /// by git or if the tag already exists. pub fn tag( - repo_path: &str, - commit_id: &CommitId, - tag: &str, + repo_path: &str, + commit_id: &CommitId, + tag: &str, ) -> Result { - scope_time!("tag"); + scope_time!("tag"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let signature = signature_allow_undefined_name(&repo)?; - let object_id = commit_id.get_oid(); - let target = - repo.find_object(object_id, Some(ObjectType::Commit))?; + let signature = signature_allow_undefined_name(&repo)?; + let object_id = commit_id.get_oid(); + let target = + repo.find_object(object_id, Some(ObjectType::Commit))?; - Ok(repo.tag(tag, &target, &signature, "", false)?.into()) + Ok(repo.tag(tag, &target, &signature, "", false)?.into()) } #[cfg(test)] mod tests { - use crate::error::Result; - use crate::sync::{ - commit, get_commit_details, get_commit_files, stage_add_file, - tags::get_tags, - tests::{get_statuses, repo_init, repo_init_empty}, - utils::get_head, - LogWalker, - }; - use commit::{amend, tag}; - use git2::Repository; - use std::{fs::File, io::Write, path::Path}; + use crate::error::Result; + use crate::sync::{ + commit, get_commit_details, get_commit_files, stage_add_file, + tags::get_tags, + tests::{get_statuses, repo_init, repo_init_empty}, + utils::get_head, + LogWalker, + }; + use commit::{amend, tag}; + use git2::Repository; + use std::{fs::File, io::Write, path::Path}; - fn count_commits(repo: &Repository, max: usize) -> usize { - let mut items = Vec::new(); - let mut walk = LogWalker::new(&repo, max).unwrap(); - walk.read(&mut items).unwrap(); - items.len() - } + fn count_commits(repo: &Repository, max: usize) -> usize { + let mut items = Vec::new(); + let mut walk = LogWalker::new(&repo, max).unwrap(); + walk.read(&mut items).unwrap(); + items.len() + } - #[test] - fn test_commit() { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_commit() { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path)) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); + File::create(&root.join(file_path)) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - assert_eq!(get_statuses(repo_path), (0, 1)); + assert_eq!(get_statuses(repo_path), (0, 1)); - commit(repo_path, "commit msg").unwrap(); + commit(repo_path, "commit msg").unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); - } + assert_eq!(get_statuses(repo_path), (0, 0)); + } - #[test] - fn test_commit_in_empty_repo() { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_commit_in_empty_repo() { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - File::create(&root.join(file_path)) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); + File::create(&root.join(file_path)) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - assert_eq!(get_statuses(repo_path), (0, 1)); + assert_eq!(get_statuses(repo_path), (0, 1)); - commit(repo_path, "commit msg").unwrap(); + commit(repo_path, "commit msg").unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); - } + assert_eq!(get_statuses(repo_path), (0, 0)); + } - #[test] - fn test_amend() -> Result<()> { - let file_path1 = Path::new("foo"); - let file_path2 = Path::new("foo2"); - let (_td, repo) = repo_init_empty()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_amend() -> Result<()> { + let file_path1 = Path::new("foo"); + let file_path2 = Path::new("foo2"); + let (_td, repo) = repo_init_empty()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path1))?.write_all(b"test1")?; + File::create(&root.join(file_path1))?.write_all(b"test1")?; - stage_add_file(repo_path, file_path1)?; - let id = commit(repo_path, "commit msg")?; + stage_add_file(repo_path, file_path1)?; + let id = commit(repo_path, "commit msg")?; - assert_eq!(count_commits(&repo, 10), 1); + assert_eq!(count_commits(&repo, 10), 1); - File::create(&root.join(file_path2))?.write_all(b"test2")?; + File::create(&root.join(file_path2))?.write_all(b"test2")?; - stage_add_file(repo_path, file_path2)?; + stage_add_file(repo_path, file_path2)?; - let new_id = amend(repo_path, id, "amended")?; + let new_id = amend(repo_path, id, "amended")?; - assert_eq!(count_commits(&repo, 10), 1); + assert_eq!(count_commits(&repo, 10), 1); - let details = get_commit_details(repo_path, new_id)?; - assert_eq!(details.message.unwrap().subject, "amended"); + let details = get_commit_details(repo_path, new_id)?; + assert_eq!(details.message.unwrap().subject, "amended"); - let files = get_commit_files(repo_path, new_id)?; + let files = get_commit_files(repo_path, new_id)?; - assert_eq!(files.len(), 2); + assert_eq!(files.len(), 2); - let head = get_head(repo_path)?; + let head = get_head(repo_path)?; - assert_eq!(head, new_id); + assert_eq!(head, new_id); - Ok(()) - } + Ok(()) + } - #[test] - fn test_tag() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_tag() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))? - .write_all(b"test\nfoo")?; + File::create(&root.join(file_path))? + .write_all(b"test\nfoo")?; - stage_add_file(repo_path, file_path)?; + stage_add_file(repo_path, file_path)?; - let new_id = commit(repo_path, "commit msg")?; + let new_id = commit(repo_path, "commit msg")?; - tag(repo_path, &new_id, "tag")?; + tag(repo_path, &new_id, "tag")?; - assert_eq!( - get_tags(repo_path).unwrap()[&new_id], - vec!["tag"] - ); + assert_eq!( + get_tags(repo_path).unwrap()[&new_id], + vec!["tag"] + ); - assert!(matches!(tag(repo_path, &new_id, "tag"), Err(_))); + assert!(matches!(tag(repo_path, &new_id, "tag"), Err(_))); - assert_eq!( - get_tags(repo_path).unwrap()[&new_id], - vec!["tag"] - ); + assert_eq!( + get_tags(repo_path).unwrap()[&new_id], + vec!["tag"] + ); - tag(repo_path, &new_id, "second-tag")?; + tag(repo_path, &new_id, "second-tag")?; - assert_eq!( - get_tags(repo_path).unwrap()[&new_id], - vec!["second-tag", "tag"] - ); + assert_eq!( + get_tags(repo_path).unwrap()[&new_id], + vec!["second-tag", "tag"] + ); - Ok(()) - } + Ok(()) + } - /// Beware: this test has to be run with a `$HOME/.gitconfig` that has - /// `user.email` not set. Otherwise, git falls back to the value of - /// `user.email` in `$HOME/.gitconfig` and this test fails. - /// - /// As of February 2021, `repo_init_empty` sets all git config locations - /// to an empty temporary directory, so this constraint is met. - #[test] - fn test_empty_email() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + /// Beware: this test has to be run with a `$HOME/.gitconfig` that has + /// `user.email` not set. Otherwise, git falls back to the value of + /// `user.email` in `$HOME/.gitconfig` and this test fails. + /// + /// As of February 2021, `repo_init_empty` sets all git config locations + /// to an empty temporary directory, so this constraint is met. + #[test] + fn test_empty_email() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))? - .write_all(b"test\nfoo")?; + File::create(&root.join(file_path))? + .write_all(b"test\nfoo")?; - stage_add_file(repo_path, file_path)?; + stage_add_file(repo_path, file_path)?; - repo.config()?.remove("user.email")?; + repo.config()?.remove("user.email")?; - let error = commit(repo_path, "commit msg"); + let error = commit(repo_path, "commit msg"); - assert!(matches!(error, Err(_))); + assert!(matches!(error, Err(_))); - repo.config()?.set_str("user.email", "email")?; + repo.config()?.set_str("user.email", "email")?; - let success = commit(repo_path, "commit msg"); + let success = commit(repo_path, "commit msg"); - assert!(matches!(success, Ok(_))); - assert_eq!(count_commits(&repo, 10), 1); + assert!(matches!(success, Ok(_))); + assert_eq!(count_commits(&repo, 10), 1); - let details = - get_commit_details(repo_path, success.unwrap()).unwrap(); + let details = + get_commit_details(repo_path, success.unwrap()).unwrap(); - assert_eq!(details.author.name, "name"); - assert_eq!(details.author.email, "email"); + assert_eq!(details.author.name, "name"); + assert_eq!(details.author.email, "email"); - Ok(()) - } + Ok(()) + } - /// See comment to `test_empty_email`. - #[test] - fn test_empty_name() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + /// See comment to `test_empty_email`. + #[test] + fn test_empty_name() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))? - .write_all(b"test\nfoo")?; + File::create(&root.join(file_path))? + .write_all(b"test\nfoo")?; - stage_add_file(repo_path, file_path)?; + stage_add_file(repo_path, file_path)?; - repo.config()?.remove("user.name")?; + repo.config()?.remove("user.name")?; - let mut success = commit(repo_path, "commit msg"); + let mut success = commit(repo_path, "commit msg"); - assert!(matches!(success, Ok(_))); - assert_eq!(count_commits(&repo, 10), 1); + assert!(matches!(success, Ok(_))); + assert_eq!(count_commits(&repo, 10), 1); - let mut details = - get_commit_details(repo_path, success.unwrap()).unwrap(); + let mut details = + get_commit_details(repo_path, success.unwrap()).unwrap(); - assert_eq!(details.author.name, "unknown"); - assert_eq!(details.author.email, "email"); + assert_eq!(details.author.name, "unknown"); + assert_eq!(details.author.email, "email"); - repo.config()?.set_str("user.name", "name")?; + repo.config()?.set_str("user.name", "name")?; - success = commit(repo_path, "commit msg"); + success = commit(repo_path, "commit msg"); - assert!(matches!(success, Ok(_))); - assert_eq!(count_commits(&repo, 10), 2); + assert!(matches!(success, Ok(_))); + assert_eq!(count_commits(&repo, 10), 2); - details = - get_commit_details(repo_path, success.unwrap()).unwrap(); + details = + get_commit_details(repo_path, success.unwrap()).unwrap(); - assert_eq!(details.author.name, "name"); - assert_eq!(details.author.email, "email"); + assert_eq!(details.author.name, "name"); + assert_eq!(details.author.email, "email"); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/commit_details.rs b/asyncgit/src/sync/commit_details.rs index b6c81515..8f74cb1e 100644 --- a/asyncgit/src/sync/commit_details.rs +++ b/asyncgit/src/sync/commit_details.rs @@ -6,166 +6,166 @@ use scopetime::scope_time; /// #[derive(Debug, PartialEq, Default, Clone)] pub struct CommitSignature { - /// - pub name: String, - /// - pub email: String, - /// time in secs since Unix epoch - pub time: i64, + /// + pub name: String, + /// + pub email: String, + /// time in secs since Unix epoch + pub time: i64, } impl CommitSignature { - /// convert from git2-rs `Signature` - pub fn from(s: &Signature<'_>) -> Self { - Self { - name: s.name().unwrap_or("").to_string(), - email: s.email().unwrap_or("").to_string(), + /// convert from git2-rs `Signature` + pub fn from(s: &Signature<'_>) -> Self { + Self { + name: s.name().unwrap_or("").to_string(), + email: s.email().unwrap_or("").to_string(), - time: s.when().seconds(), - } - } + time: s.when().seconds(), + } + } } /// #[derive(Default, Clone)] pub struct CommitMessage { - /// first line - pub subject: String, - /// remaining lines if more than one - pub body: Option, + /// first line + pub subject: String, + /// remaining lines if more than one + pub body: Option, } impl CommitMessage { - /// - pub fn from(s: &str) -> Self { - let mut lines = s.lines(); - let subject = lines.next().map_or_else( - String::new, - std::string::ToString::to_string, - ); + /// + pub fn from(s: &str) -> Self { + let mut lines = s.lines(); + let subject = lines.next().map_or_else( + String::new, + std::string::ToString::to_string, + ); - let body: Vec = - lines.map(std::string::ToString::to_string).collect(); + let body: Vec = + lines.map(std::string::ToString::to_string).collect(); - Self { - subject, - body: if body.is_empty() { - None - } else { - Some(body.join("\n")) - }, - } - } + Self { + subject, + body: if body.is_empty() { + None + } else { + Some(body.join("\n")) + }, + } + } - /// - pub fn combine(self) -> String { - if let Some(body) = self.body { - format!("{}\n{}", self.subject, body) - } else { - self.subject - } - } + /// + pub fn combine(self) -> String { + if let Some(body) = self.body { + format!("{}\n{}", self.subject, body) + } else { + self.subject + } + } } /// #[derive(Default, Clone)] pub struct CommitDetails { - /// - pub author: CommitSignature, - /// committer when differs to `author` otherwise None - pub committer: Option, - /// - pub message: Option, - /// - pub hash: String, + /// + pub author: CommitSignature, + /// committer when differs to `author` otherwise None + pub committer: Option, + /// + pub message: Option, + /// + pub hash: String, } /// pub fn get_commit_details( - repo_path: &str, - id: CommitId, + repo_path: &str, + id: CommitId, ) -> Result { - scope_time!("get_commit_details"); + scope_time!("get_commit_details"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let commit = repo.find_commit(id.into())?; + let commit = repo.find_commit(id.into())?; - let author = CommitSignature::from(&commit.author()); - let committer = CommitSignature::from(&commit.committer()); - let committer = if author == committer { - None - } else { - Some(committer) - }; + let author = CommitSignature::from(&commit.author()); + let committer = CommitSignature::from(&commit.committer()); + let committer = if author == committer { + None + } else { + Some(committer) + }; - let msg = - CommitMessage::from(get_message(&commit, None).as_str()); + let msg = + CommitMessage::from(get_message(&commit, None).as_str()); - let details = CommitDetails { - author, - committer, - message: Some(msg), - hash: id.to_string(), - }; + let details = CommitDetails { + author, + committer, + message: Some(msg), + hash: id.to_string(), + }; - Ok(details) + Ok(details) } #[cfg(test)] mod tests { - use super::{get_commit_details, CommitMessage}; - use crate::error::Result; - use crate::sync::{ - commit, stage_add_file, tests::repo_init_empty, - }; - use std::{fs::File, io::Write, path::Path}; + use super::{get_commit_details, CommitMessage}; + use crate::error::Result; + use crate::sync::{ + commit, stage_add_file, tests::repo_init_empty, + }; + use std::{fs::File, io::Write, path::Path}; - #[test] - fn test_msg_invalid_utf8() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_msg_invalid_utf8() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); - let msg = invalidstring::invalid_utf8("test msg"); - let id = commit(repo_path, msg.as_str()).unwrap(); + let msg = invalidstring::invalid_utf8("test msg"); + let id = commit(repo_path, msg.as_str()).unwrap(); - let res = get_commit_details(repo_path, id).unwrap(); + let res = get_commit_details(repo_path, id).unwrap(); - dbg!(&res.message.as_ref().unwrap().subject); - assert_eq!( - res.message - .as_ref() - .unwrap() - .subject - .starts_with("test msg"), - true - ); + dbg!(&res.message.as_ref().unwrap().subject); + assert_eq!( + res.message + .as_ref() + .unwrap() + .subject + .starts_with("test msg"), + true + ); - Ok(()) - } + Ok(()) + } - #[test] - fn test_msg_linefeeds() -> Result<()> { - let msg = CommitMessage::from("foo\nbar\r\ntest"); + #[test] + fn test_msg_linefeeds() -> Result<()> { + let msg = CommitMessage::from("foo\nbar\r\ntest"); - assert_eq!(msg.subject, String::from("foo"),); - assert_eq!(msg.body, Some(String::from("bar\ntest")),); + assert_eq!(msg.subject, String::from("foo"),); + assert_eq!(msg.body, Some(String::from("bar\ntest")),); - Ok(()) - } + Ok(()) + } - #[test] - fn test_commit_message_combine() -> Result<()> { - let msg = CommitMessage::from("foo\nbar\r\ntest"); + #[test] + fn test_commit_message_combine() -> Result<()> { + let msg = CommitMessage::from("foo\nbar\r\ntest"); - assert_eq!(msg.combine(), String::from("foo\nbar\ntest")); + assert_eq!(msg.combine(), String::from("foo\nbar\ntest")); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/commit_files.rs b/asyncgit/src/sync/commit_files.rs index ca9a1224..ee2bb062 100644 --- a/asyncgit/src/sync/commit_files.rs +++ b/asyncgit/src/sync/commit_files.rs @@ -1,175 +1,175 @@ use super::{stash::is_stash_commit, utils::repo, CommitId}; use crate::{ - error::Error, error::Result, StatusItem, StatusItemType, + error::Error, error::Result, StatusItem, StatusItemType, }; use git2::{Diff, DiffDelta, DiffOptions, Repository}; use scopetime::scope_time; /// get all files that are part of a commit pub fn get_commit_files( - repo_path: &str, - id: CommitId, + repo_path: &str, + id: CommitId, ) -> Result> { - scope_time!("get_commit_files"); + scope_time!("get_commit_files"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let diff = get_commit_diff(&repo, id, None)?; + let diff = get_commit_diff(&repo, id, None)?; - let mut res = Vec::new(); + let mut res = Vec::new(); - diff.foreach( - &mut |delta: DiffDelta<'_>, _progress| { - res.push(StatusItem { - path: delta - .new_file() - .path() - .map(|p| p.to_str().unwrap_or("").to_string()) - .unwrap_or_default(), - status: StatusItemType::from(delta.status()), - }); - true - }, - None, - None, - None, - )?; + diff.foreach( + &mut |delta: DiffDelta<'_>, _progress| { + res.push(StatusItem { + path: delta + .new_file() + .path() + .map(|p| p.to_str().unwrap_or("").to_string()) + .unwrap_or_default(), + status: StatusItemType::from(delta.status()), + }); + true + }, + None, + None, + None, + )?; - Ok(res) + Ok(res) } #[allow(clippy::redundant_pub_crate)] pub(crate) fn get_commit_diff( - repo: &Repository, - id: CommitId, - pathspec: Option, + repo: &Repository, + id: CommitId, + pathspec: Option, ) -> Result> { - // scope_time!("get_commit_diff"); + // scope_time!("get_commit_diff"); - let commit = repo.find_commit(id.into())?; - let commit_tree = commit.tree()?; - let parent = if commit.parent_count() > 0 { - repo.find_commit(commit.parent_id(0)?) - .ok() - .and_then(|c| c.tree().ok()) - } else { - None - }; + let commit = repo.find_commit(id.into())?; + let commit_tree = commit.tree()?; + let parent = if commit.parent_count() > 0 { + repo.find_commit(commit.parent_id(0)?) + .ok() + .and_then(|c| c.tree().ok()) + } else { + None + }; - let mut opts = DiffOptions::new(); - if let Some(p) = &pathspec { - opts.pathspec(p.clone()); - } - opts.show_binary(true); + let mut opts = DiffOptions::new(); + if let Some(p) = &pathspec { + opts.pathspec(p.clone()); + } + opts.show_binary(true); - let mut diff = repo.diff_tree_to_tree( - parent.as_ref(), - Some(&commit_tree), - Some(&mut opts), - )?; + let mut diff = repo.diff_tree_to_tree( + parent.as_ref(), + Some(&commit_tree), + Some(&mut opts), + )?; - if is_stash_commit( - repo.path().to_str().map_or_else( - || Err(Error::Generic("repo path utf8 err".to_owned())), - Ok, - )?, - &id, - )? { - if let Ok(untracked_commit) = commit.parent_id(2) { - let untracked_diff = get_commit_diff( - repo, - CommitId::new(untracked_commit), - pathspec, - )?; + if is_stash_commit( + repo.path().to_str().map_or_else( + || Err(Error::Generic("repo path utf8 err".to_owned())), + Ok, + )?, + &id, + )? { + if let Ok(untracked_commit) = commit.parent_id(2) { + let untracked_diff = get_commit_diff( + repo, + CommitId::new(untracked_commit), + pathspec, + )?; - diff.merge(&untracked_diff)?; - } - } + diff.merge(&untracked_diff)?; + } + } - Ok(diff) + Ok(diff) } #[cfg(test)] mod tests { - use super::get_commit_files; - use crate::{ - error::Result, - sync::{ - commit, stage_add_file, stash_save, - tests::{get_statuses, repo_init}, - }, - StatusItemType, - }; - use std::{fs::File, io::Write, path::Path}; + use super::get_commit_files; + use crate::{ + error::Result, + sync::{ + commit, stage_add_file, stash_save, + tests::{get_statuses, repo_init}, + }, + StatusItemType, + }; + use std::{fs::File, io::Write, path::Path}; - #[test] - fn test_smoke() -> Result<()> { - let file_path = Path::new("file1.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() -> Result<()> { + let file_path = Path::new("file1.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))? - .write_all(b"test file1 content")?; + File::create(&root.join(file_path))? + .write_all(b"test file1 content")?; - stage_add_file(repo_path, file_path)?; + stage_add_file(repo_path, file_path)?; - let id = commit(repo_path, "commit msg")?; + let id = commit(repo_path, "commit msg")?; - let diff = get_commit_files(repo_path, id)?; + let diff = get_commit_files(repo_path, id)?; - assert_eq!(diff.len(), 1); - assert_eq!(diff[0].status, StatusItemType::New); + assert_eq!(diff.len(), 1); + assert_eq!(diff[0].status, StatusItemType::New); - Ok(()) - } + Ok(()) + } - #[test] - fn test_stashed_untracked() -> Result<()> { - let file_path = Path::new("file1.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stashed_untracked() -> Result<()> { + let file_path = Path::new("file1.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))? - .write_all(b"test file1 content")?; + File::create(&root.join(file_path))? + .write_all(b"test file1 content")?; - let id = stash_save(repo_path, None, true, false)?; + let id = stash_save(repo_path, None, true, false)?; - let diff = get_commit_files(repo_path, id)?; + let diff = get_commit_files(repo_path, id)?; - assert_eq!(diff.len(), 1); - assert_eq!(diff[0].status, StatusItemType::New); + assert_eq!(diff.len(), 1); + assert_eq!(diff[0].status, StatusItemType::New); - Ok(()) - } + Ok(()) + } - #[test] - fn test_stashed_untracked_and_modified() -> Result<()> { - let file_path1 = Path::new("file1.txt"); - let file_path2 = Path::new("file2.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stashed_untracked_and_modified() -> Result<()> { + let file_path1 = Path::new("file1.txt"); + let file_path2 = Path::new("file2.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path1))?.write_all(b"test")?; - stage_add_file(repo_path, file_path1)?; - commit(repo_path, "c1")?; + File::create(&root.join(file_path1))?.write_all(b"test")?; + stage_add_file(repo_path, file_path1)?; + commit(repo_path, "c1")?; - File::create(&root.join(file_path1))? - .write_all(b"modified")?; - File::create(&root.join(file_path2))?.write_all(b"new")?; + File::create(&root.join(file_path1))? + .write_all(b"modified")?; + File::create(&root.join(file_path2))?.write_all(b"new")?; - assert_eq!(get_statuses(repo_path), (2, 0)); + assert_eq!(get_statuses(repo_path), (2, 0)); - let id = stash_save(repo_path, None, true, false)?; + let id = stash_save(repo_path, None, true, false)?; - let diff = get_commit_files(repo_path, id)?; + let diff = get_commit_files(repo_path, id)?; - assert_eq!(diff.len(), 2); - assert_eq!(diff[0].status, StatusItemType::Modified); - assert_eq!(diff[1].status, StatusItemType::New); + assert_eq!(diff.len(), 2); + assert_eq!(diff[0].status, StatusItemType::Modified); + assert_eq!(diff[1].status, StatusItemType::New); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/commits_info.rs b/asyncgit/src/sync/commits_info.rs index c1058715..c9e3a54a 100644 --- a/asyncgit/src/sync/commits_info.rs +++ b/asyncgit/src/sync/commits_info.rs @@ -6,211 +6,211 @@ use unicode_truncate::UnicodeTruncateStr; /// identifies a single commit #[derive( - Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, + Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, )] pub struct CommitId(Oid); impl CommitId { - /// create new `CommitId` - pub const fn new(id: Oid) -> Self { - Self(id) - } + /// create new `CommitId` + pub const fn new(id: Oid) -> Self { + Self(id) + } - /// - pub(crate) const fn get_oid(self) -> Oid { - self.0 - } + /// + pub(crate) const fn get_oid(self) -> Oid { + self.0 + } - /// 7 chars short hash - pub fn get_short_string(&self) -> String { - self.to_string().chars().take(7).collect() - } + /// 7 chars short hash + pub fn get_short_string(&self) -> String { + self.to_string().chars().take(7).collect() + } } //TODO: remove once clippy fixed: https://github.com/rust-lang/rust-clippy/issues/6983 #[allow(clippy::wrong_self_convention)] impl ToString for CommitId { - fn to_string(&self) -> String { - self.0.to_string() - } + fn to_string(&self) -> String { + self.0.to_string() + } } impl From for Oid { - fn from(id: CommitId) -> Self { - id.0 - } + fn from(id: CommitId) -> Self { + id.0 + } } impl From for CommitId { - fn from(id: Oid) -> Self { - Self::new(id) - } + fn from(id: Oid) -> Self { + Self::new(id) + } } /// #[derive(Debug)] pub struct CommitInfo { - /// - pub message: String, - /// - pub time: i64, - /// - pub author: String, - /// - pub id: CommitId, + /// + pub message: String, + /// + pub time: i64, + /// + pub author: String, + /// + pub id: CommitId, } /// pub fn get_commits_info( - repo_path: &str, - ids: &[CommitId], - message_length_limit: usize, + repo_path: &str, + ids: &[CommitId], + message_length_limit: usize, ) -> Result> { - scope_time!("get_commits_info"); + scope_time!("get_commits_info"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let commits = ids - .iter() - .map(|id| repo.find_commit((*id).into())) - .collect::, Error>>()? - .into_iter(); + let commits = ids + .iter() + .map(|id| repo.find_commit((*id).into())) + .collect::, Error>>()? + .into_iter(); - let res = commits - .map(|c: Commit| { - let message = get_message(&c, Some(message_length_limit)); - let author = c.author().name().map_or_else( - || String::from(""), - String::from, - ); - CommitInfo { - message, - author, - time: c.time().seconds(), - id: CommitId(c.id()), - } - }) - .collect::>(); + let res = commits + .map(|c: Commit| { + let message = get_message(&c, Some(message_length_limit)); + let author = c.author().name().map_or_else( + || String::from(""), + String::from, + ); + CommitInfo { + message, + author, + time: c.time().seconds(), + id: CommitId(c.id()), + } + }) + .collect::>(); - Ok(res) + Ok(res) } /// pub fn get_commit_info( - repo_path: &str, - commit_id: &CommitId, + repo_path: &str, + commit_id: &CommitId, ) -> Result { - scope_time!("get_commit_info"); + scope_time!("get_commit_info"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let commit = repo.find_commit((*commit_id).into())?; - let author = commit.author(); + let commit = repo.find_commit((*commit_id).into())?; + let author = commit.author(); - Ok(CommitInfo { - message: commit.message().unwrap_or("").into(), - author: author.name().unwrap_or("").into(), - time: commit.time().seconds(), - id: CommitId(commit.id()), - }) + Ok(CommitInfo { + message: commit.message().unwrap_or("").into(), + author: author.name().unwrap_or("").into(), + time: commit.time().seconds(), + id: CommitId(commit.id()), + }) } /// if `message_limit` is set the message will be /// limited to the first line and truncated to fit pub fn get_message( - c: &Commit, - message_limit: Option, + c: &Commit, + message_limit: Option, ) -> String { - let msg = String::from_utf8_lossy(c.message_bytes()); - let msg = msg.trim(); + let msg = String::from_utf8_lossy(c.message_bytes()); + let msg = msg.trim(); - message_limit.map_or_else( - || msg.to_string(), - |limit| { - let msg = msg.lines().next().unwrap_or_default(); - msg.unicode_truncate(limit).0.to_string() - }, - ) + message_limit.map_or_else( + || msg.to_string(), + |limit| { + let msg = msg.lines().next().unwrap_or_default(); + msg.unicode_truncate(limit).0.to_string() + }, + ) } #[cfg(test)] mod tests { - use super::get_commits_info; - use crate::error::Result; - use crate::sync::{ - commit, stage_add_file, tests::repo_init_empty, - utils::get_head_repo, - }; - use std::{fs::File, io::Write, path::Path}; + use super::get_commits_info; + use crate::error::Result; + use crate::sync::{ + commit, stage_add_file, tests::repo_init_empty, + utils::get_head_repo, + }; + use std::{fs::File, io::Write, path::Path}; - #[test] - fn test_log() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_log() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - let c1 = commit(repo_path, "commit1").unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - let c2 = commit(repo_path, "commit2").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + let c1 = commit(repo_path, "commit1").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + let c2 = commit(repo_path, "commit2").unwrap(); - let res = - get_commits_info(repo_path, &vec![c2, c1], 50).unwrap(); + let res = + get_commits_info(repo_path, &vec![c2, c1], 50).unwrap(); - assert_eq!(res.len(), 2); - assert_eq!(res[0].message.as_str(), "commit2"); - assert_eq!(res[0].author.as_str(), "name"); - assert_eq!(res[1].message.as_str(), "commit1"); + assert_eq!(res.len(), 2); + assert_eq!(res[0].message.as_str(), "commit2"); + assert_eq!(res[0].author.as_str(), "name"); + assert_eq!(res[1].message.as_str(), "commit1"); - Ok(()) - } + Ok(()) + } - #[test] - fn test_log_first_msg_line() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_log_first_msg_line() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - let c1 = commit(repo_path, "subject\nbody").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + let c1 = commit(repo_path, "subject\nbody").unwrap(); - let res = get_commits_info(repo_path, &vec![c1], 50).unwrap(); + let res = get_commits_info(repo_path, &vec![c1], 50).unwrap(); - assert_eq!(res.len(), 1); - assert_eq!(res[0].message.as_str(), "subject"); + assert_eq!(res.len(), 1); + assert_eq!(res[0].message.as_str(), "subject"); - Ok(()) - } + Ok(()) + } - #[test] - fn test_invalid_utf8() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_invalid_utf8() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); - let msg = invalidstring::invalid_utf8("test msg"); - commit(repo_path, msg.as_str()).unwrap(); + let msg = invalidstring::invalid_utf8("test msg"); + commit(repo_path, msg.as_str()).unwrap(); - let res = get_commits_info( - repo_path, - &vec![get_head_repo(&repo).unwrap().into()], - 50, - ) - .unwrap(); + let res = get_commits_info( + repo_path, + &vec![get_head_repo(&repo).unwrap().into()], + 50, + ) + .unwrap(); - assert_eq!(res.len(), 1); - dbg!(&res[0].message); - assert_eq!(res[0].message.starts_with("test msg"), true); + assert_eq!(res.len(), 1); + dbg!(&res[0].message); + assert_eq!(res[0].message.starts_with("test msg"), true); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/config.rs b/asyncgit/src/sync/config.rs index dc6369bc..dacb380f 100644 --- a/asyncgit/src/sync/config.rs +++ b/asyncgit/src/sync/config.rs @@ -6,109 +6,109 @@ use scopetime::scope_time; // see https://git-scm.com/docs/git-config#Documentation/git-config.txt-statusshowUntrackedFiles /// represents the `status.showUntrackedFiles` git config state pub enum ShowUntrackedFilesConfig { - /// - No, - /// - Normal, - /// - All, + /// + No, + /// + Normal, + /// + All, } impl ShowUntrackedFilesConfig { - /// - pub const fn include_none(&self) -> bool { - matches!(self, Self::No) - } + /// + pub const fn include_none(&self) -> bool { + matches!(self, Self::No) + } - /// - pub const fn include_untracked(&self) -> bool { - matches!(self, Self::Normal | Self::All) - } + /// + pub const fn include_untracked(&self) -> bool { + matches!(self, Self::Normal | Self::All) + } - /// - pub const fn recurse_untracked_dirs(&self) -> bool { - matches!(self, Self::All) - } + /// + pub const fn recurse_untracked_dirs(&self) -> bool { + matches!(self, Self::All) + } } pub fn untracked_files_config_repo( - repo: &Repository, + repo: &Repository, ) -> Result { - let show_untracked_files = - get_config_string_repo(repo, "status.showUntrackedFiles")?; + let show_untracked_files = + get_config_string_repo(repo, "status.showUntrackedFiles")?; - if let Some(show_untracked_files) = show_untracked_files { - if &show_untracked_files == "no" { - return Ok(ShowUntrackedFilesConfig::No); - } else if &show_untracked_files == "normal" { - return Ok(ShowUntrackedFilesConfig::Normal); - } - } + if let Some(show_untracked_files) = show_untracked_files { + if &show_untracked_files == "no" { + return Ok(ShowUntrackedFilesConfig::No); + } else if &show_untracked_files == "normal" { + return Ok(ShowUntrackedFilesConfig::Normal); + } + } - Ok(ShowUntrackedFilesConfig::All) + Ok(ShowUntrackedFilesConfig::All) } /// pub fn untracked_files_config( - repo_path: &str, + repo_path: &str, ) -> Result { - let repo = repo(repo_path)?; - untracked_files_config_repo(&repo) + let repo = repo(repo_path)?; + untracked_files_config_repo(&repo) } /// get string from config pub fn get_config_string( - repo_path: &str, - key: &str, + repo_path: &str, + key: &str, ) -> Result> { - let repo = repo(repo_path)?; - get_config_string_repo(&repo, key) + let repo = repo(repo_path)?; + get_config_string_repo(&repo, key) } pub fn get_config_string_repo( - repo: &Repository, - key: &str, + repo: &Repository, + key: &str, ) -> Result> { - scope_time!("get_config_string_repo"); + scope_time!("get_config_string_repo"); - let cfg = repo.config()?; + let cfg = repo.config()?; - // this code doesnt match what the doc says regarding what - // gets returned when but it actually works - let entry_res = cfg.get_entry(key); + // this code doesnt match what the doc says regarding what + // gets returned when but it actually works + let entry_res = cfg.get_entry(key); - let entry = match entry_res { - Ok(ent) => ent, - Err(_) => return Ok(None), - }; + let entry = match entry_res { + Ok(ent) => ent, + Err(_) => return Ok(None), + }; - if entry.has_value() { - Ok(entry.value().map(std::string::ToString::to_string)) - } else { - Ok(None) - } + if entry.has_value() { + Ok(entry.value().map(std::string::ToString::to_string)) + } else { + Ok(None) + } } #[cfg(test)] mod tests { - use super::*; - use crate::sync::tests::repo_init; + use super::*; + use crate::sync::tests::repo_init; - #[test] - fn test_get_config() { - let bad_dir_cfg = - get_config_string("oodly_noodly", "this.doesnt.exist"); - assert!(bad_dir_cfg.is_err()); + #[test] + fn test_get_config() { + let bad_dir_cfg = + get_config_string("oodly_noodly", "this.doesnt.exist"); + assert!(bad_dir_cfg.is_err()); - let (_td, repo) = repo_init().unwrap(); - let path = repo.path(); - let rpath = path.as_os_str().to_str().unwrap(); - let bad_cfg = get_config_string(rpath, "this.doesnt.exist"); - assert!(bad_cfg.is_ok()); - assert!(bad_cfg.unwrap().is_none()); - // repo init sets user.name - let good_cfg = get_config_string(rpath, "user.name"); - assert!(good_cfg.is_ok()); - assert!(good_cfg.unwrap().is_some()); - } + let (_td, repo) = repo_init().unwrap(); + let path = repo.path(); + let rpath = path.as_os_str().to_str().unwrap(); + let bad_cfg = get_config_string(rpath, "this.doesnt.exist"); + assert!(bad_cfg.is_ok()); + assert!(bad_cfg.unwrap().is_none()); + // repo init sets user.name + let good_cfg = get_config_string(rpath, "user.name"); + assert!(good_cfg.is_ok()); + assert!(good_cfg.unwrap().is_some()); + } } diff --git a/asyncgit/src/sync/cred.rs b/asyncgit/src/sync/cred.rs index e5d87520..52e524d2 100644 --- a/asyncgit/src/sync/cred.rs +++ b/asyncgit/src/sync/cred.rs @@ -2,252 +2,252 @@ use super::remotes::get_default_remote_in_repo; use crate::{ - error::{Error, Result}, - CWD, + error::{Error, Result}, + CWD, }; use git2::{Config, CredentialHelper}; /// basic Authentication Credentials #[derive(Debug, Clone, Default, PartialEq)] pub struct BasicAuthCredential { - /// - pub username: Option, - /// - pub password: Option, + /// + pub username: Option, + /// + pub password: Option, } impl BasicAuthCredential { - /// - pub const fn is_complete(&self) -> bool { - self.username.is_some() && self.password.is_some() - } - /// - pub const fn new( - username: Option, - password: Option, - ) -> Self { - Self { username, password } - } + /// + pub const fn is_complete(&self) -> bool { + self.username.is_some() && self.password.is_some() + } + /// + pub const fn new( + username: Option, + password: Option, + ) -> Self { + Self { username, password } + } } /// know if username and password are needed for this url pub fn need_username_password() -> Result { - let repo = crate::sync::utils::repo(CWD)?; - let url = repo - .find_remote(&get_default_remote_in_repo(&repo)?)? - .url() - .ok_or(Error::UnknownRemote)? - .to_owned(); - let is_http = url.starts_with("http"); - Ok(is_http) + let repo = crate::sync::utils::repo(CWD)?; + let url = repo + .find_remote(&get_default_remote_in_repo(&repo)?)? + .url() + .ok_or(Error::UnknownRemote)? + .to_owned(); + let is_http = url.starts_with("http"); + Ok(is_http) } /// extract username and password pub fn extract_username_password() -> Result { - let repo = crate::sync::utils::repo(CWD)?; - let url = repo - .find_remote(&get_default_remote_in_repo(&repo)?)? - .url() - .ok_or(Error::UnknownRemote)? - .to_owned(); - let mut helper = CredentialHelper::new(&url); + let repo = crate::sync::utils::repo(CWD)?; + let url = repo + .find_remote(&get_default_remote_in_repo(&repo)?)? + .url() + .ok_or(Error::UnknownRemote)? + .to_owned(); + let mut helper = CredentialHelper::new(&url); - if let Ok(config) = Config::open_default() { - helper.config(&config); - } - Ok(match helper.execute() { - Some((username, password)) => { - BasicAuthCredential::new(Some(username), Some(password)) - } - None => extract_cred_from_url(&url), - }) + if let Ok(config) = Config::open_default() { + helper.config(&config); + } + Ok(match helper.execute() { + Some((username, password)) => { + BasicAuthCredential::new(Some(username), Some(password)) + } + None => extract_cred_from_url(&url), + }) } /// extract credentials from url pub fn extract_cred_from_url(url: &str) -> BasicAuthCredential { - if let Ok(url) = url::Url::parse(url) { - BasicAuthCredential::new( - if url.username() == "" { - None - } else { - Some(url.username().to_owned()) - }, - url.password().map(std::borrow::ToOwned::to_owned), - ) - } else { - BasicAuthCredential::new(None, None) - } + if let Ok(url) = url::Url::parse(url) { + BasicAuthCredential::new( + if url.username() == "" { + None + } else { + Some(url.username().to_owned()) + }, + url.password().map(std::borrow::ToOwned::to_owned), + ) + } else { + BasicAuthCredential::new(None, None) + } } #[cfg(test)] mod tests { - use crate::sync::{ - cred::{ - extract_cred_from_url, extract_username_password, - need_username_password, BasicAuthCredential, - }, - remotes::DEFAULT_REMOTE_NAME, - tests::repo_init, - }; - use serial_test::serial; - use std::env; + use crate::sync::{ + cred::{ + extract_cred_from_url, extract_username_password, + need_username_password, BasicAuthCredential, + }, + remotes::DEFAULT_REMOTE_NAME, + tests::repo_init, + }; + use serial_test::serial; + use std::env; - #[test] - fn test_credential_complete() { - assert_eq!( - BasicAuthCredential::new( - Some("username".to_owned()), - Some("password".to_owned()) - ) - .is_complete(), - true - ); - } + #[test] + fn test_credential_complete() { + assert_eq!( + BasicAuthCredential::new( + Some("username".to_owned()), + Some("password".to_owned()) + ) + .is_complete(), + true + ); + } - #[test] - fn test_credential_not_complete() { - assert_eq!( - BasicAuthCredential::new( - None, - Some("password".to_owned()) - ) - .is_complete(), - false - ); - assert_eq!( - BasicAuthCredential::new( - Some("username".to_owned()), - None - ) - .is_complete(), - false - ); - assert_eq!( - BasicAuthCredential::new(None, None).is_complete(), - false - ); - } + #[test] + fn test_credential_not_complete() { + assert_eq!( + BasicAuthCredential::new( + None, + Some("password".to_owned()) + ) + .is_complete(), + false + ); + assert_eq!( + BasicAuthCredential::new( + Some("username".to_owned()), + None + ) + .is_complete(), + false + ); + assert_eq!( + BasicAuthCredential::new(None, None).is_complete(), + false + ); + } - #[test] - fn test_extract_username_from_url() { - assert_eq!( - extract_cred_from_url("https://user@github.com"), - BasicAuthCredential::new(Some("user".to_owned()), None) - ); - } + #[test] + fn test_extract_username_from_url() { + assert_eq!( + extract_cred_from_url("https://user@github.com"), + BasicAuthCredential::new(Some("user".to_owned()), None) + ); + } - #[test] - fn test_extract_username_password_from_url() { - assert_eq!( - extract_cred_from_url("https://user:pwd@github.com"), - BasicAuthCredential::new( - Some("user".to_owned()), - Some("pwd".to_owned()) - ) - ); - } + #[test] + fn test_extract_username_password_from_url() { + assert_eq!( + extract_cred_from_url("https://user:pwd@github.com"), + BasicAuthCredential::new( + Some("user".to_owned()), + Some("pwd".to_owned()) + ) + ); + } - #[test] - fn test_extract_nothing_from_url() { - assert_eq!( - extract_cred_from_url("https://github.com"), - BasicAuthCredential::new(None, None) - ); - } + #[test] + fn test_extract_nothing_from_url() { + assert_eq!( + extract_cred_from_url("https://github.com"), + BasicAuthCredential::new(None, None) + ); + } - #[test] - #[serial] - fn test_need_username_password_if_https() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + #[serial] + fn test_need_username_password_if_https() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - env::set_current_dir(repo_path).unwrap(); - repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com") - .unwrap(); + env::set_current_dir(repo_path).unwrap(); + repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com") + .unwrap(); - assert_eq!(need_username_password().unwrap(), true); - } + assert_eq!(need_username_password().unwrap(), true); + } - #[test] - #[serial] - fn test_dont_need_username_password_if_ssh() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + #[serial] + fn test_dont_need_username_password_if_ssh() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - env::set_current_dir(repo_path).unwrap(); - repo.remote(DEFAULT_REMOTE_NAME, "git@github.com:user/repo") - .unwrap(); + env::set_current_dir(repo_path).unwrap(); + repo.remote(DEFAULT_REMOTE_NAME, "git@github.com:user/repo") + .unwrap(); - assert_eq!(need_username_password().unwrap(), false); - } + assert_eq!(need_username_password().unwrap(), false); + } - #[test] - #[serial] - #[should_panic] - fn test_error_if_no_remote_when_trying_to_retrieve_if_need_username_password( - ) { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + #[serial] + #[should_panic] + fn test_error_if_no_remote_when_trying_to_retrieve_if_need_username_password( + ) { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - env::set_current_dir(repo_path).unwrap(); + env::set_current_dir(repo_path).unwrap(); - need_username_password().unwrap(); - } + need_username_password().unwrap(); + } - #[test] - #[serial] - fn test_extract_username_password_from_repo() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + #[serial] + fn test_extract_username_password_from_repo() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - env::set_current_dir(repo_path).unwrap(); - repo.remote( - DEFAULT_REMOTE_NAME, - "http://user:pass@github.com", - ) - .unwrap(); + env::set_current_dir(repo_path).unwrap(); + repo.remote( + DEFAULT_REMOTE_NAME, + "http://user:pass@github.com", + ) + .unwrap(); - assert_eq!( - extract_username_password().unwrap(), - BasicAuthCredential::new( - Some("user".to_owned()), - Some("pass".to_owned()) - ) - ); - } + assert_eq!( + extract_username_password().unwrap(), + BasicAuthCredential::new( + Some("user".to_owned()), + Some("pass".to_owned()) + ) + ); + } - #[test] - #[serial] - fn test_extract_username_from_repo() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + #[serial] + fn test_extract_username_from_repo() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - env::set_current_dir(repo_path).unwrap(); - repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com") - .unwrap(); + env::set_current_dir(repo_path).unwrap(); + repo.remote(DEFAULT_REMOTE_NAME, "http://user@github.com") + .unwrap(); - assert_eq!( - extract_username_password().unwrap(), - BasicAuthCredential::new(Some("user".to_owned()), None) - ); - } + assert_eq!( + extract_username_password().unwrap(), + BasicAuthCredential::new(Some("user".to_owned()), None) + ); + } - #[test] - #[serial] - #[should_panic] - fn test_error_if_no_remote_when_trying_to_extract_username_password( - ) { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + #[serial] + #[should_panic] + fn test_error_if_no_remote_when_trying_to_extract_username_password( + ) { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - env::set_current_dir(repo_path).unwrap(); + env::set_current_dir(repo_path).unwrap(); - extract_username_password().unwrap(); - } + extract_username_password().unwrap(); + } } diff --git a/asyncgit/src/sync/diff.rs b/asyncgit/src/sync/diff.rs index 9fb935de..6c5750b0 100644 --- a/asyncgit/src/sync/diff.rs +++ b/asyncgit/src/sync/diff.rs @@ -1,15 +1,15 @@ //! sync git api for fetching a diff use super::{ - commit_files::get_commit_diff, - utils::{self, get_head_repo, work_dir}, - CommitId, + commit_files::get_commit_diff, + utils::{self, get_head_repo, work_dir}, + CommitId, }; use crate::{error::Error, error::Result, hash}; use easy_cast::Conv; use git2::{ - Delta, Diff, DiffDelta, DiffFormat, DiffHunk, DiffOptions, Patch, - Repository, + Delta, Diff, DiffDelta, DiffFormat, DiffHunk, DiffOptions, Patch, + Repository, }; use scopetime::scope_time; use std::{cell::RefCell, fs, path::Path, rc::Rc}; @@ -17,406 +17,409 @@ use std::{cell::RefCell, fs, path::Path, rc::Rc}; /// type of diff of a single line #[derive(Copy, Clone, PartialEq, Hash, Debug)] pub enum DiffLineType { - /// just surrounding line, no change - None, - /// header of the hunk - Header, - /// line added - Add, - /// line deleted - Delete, + /// just surrounding line, no change + None, + /// header of the hunk + Header, + /// line added + Add, + /// line deleted + Delete, } impl From for DiffLineType { - fn from(line_type: git2::DiffLineType) -> Self { - match line_type { - git2::DiffLineType::HunkHeader => Self::Header, - git2::DiffLineType::DeleteEOFNL - | git2::DiffLineType::Deletion => Self::Delete, - git2::DiffLineType::AddEOFNL - | git2::DiffLineType::Addition => Self::Add, - _ => Self::None, - } - } + fn from(line_type: git2::DiffLineType) -> Self { + match line_type { + git2::DiffLineType::HunkHeader => Self::Header, + git2::DiffLineType::DeleteEOFNL + | git2::DiffLineType::Deletion => Self::Delete, + git2::DiffLineType::AddEOFNL + | git2::DiffLineType::Addition => Self::Add, + _ => Self::None, + } + } } impl Default for DiffLineType { - fn default() -> Self { - Self::None - } + fn default() -> Self { + Self::None + } } /// #[derive(Default, Clone, Hash, Debug)] pub struct DiffLine { - /// - pub content: String, - /// - pub line_type: DiffLineType, - /// - pub position: DiffLinePosition, + /// + pub content: String, + /// + pub line_type: DiffLineType, + /// + pub position: DiffLinePosition, } /// #[derive(Clone, Copy, Default, Hash, Debug, PartialEq, Eq)] pub struct DiffLinePosition { - /// - pub old_lineno: Option, - /// - pub new_lineno: Option, + /// + pub old_lineno: Option, + /// + pub new_lineno: Option, } impl PartialEq<&git2::DiffLine<'_>> for DiffLinePosition { - fn eq(&self, other: &&git2::DiffLine) -> bool { - other.new_lineno() == self.new_lineno - && other.old_lineno() == self.old_lineno - } + fn eq(&self, other: &&git2::DiffLine) -> bool { + other.new_lineno() == self.new_lineno + && other.old_lineno() == self.old_lineno + } } impl From<&git2::DiffLine<'_>> for DiffLinePosition { - fn from(line: &git2::DiffLine<'_>) -> Self { - Self { - old_lineno: line.old_lineno(), - new_lineno: line.new_lineno(), - } - } + fn from(line: &git2::DiffLine<'_>) -> Self { + Self { + old_lineno: line.old_lineno(), + new_lineno: line.new_lineno(), + } + } } #[derive(Debug, Default, Clone, Copy, PartialEq, Hash)] pub(crate) struct HunkHeader { - pub old_start: u32, - pub old_lines: u32, - pub new_start: u32, - pub new_lines: u32, + pub old_start: u32, + pub old_lines: u32, + pub new_start: u32, + pub new_lines: u32, } impl From> for HunkHeader { - fn from(h: DiffHunk) -> Self { - Self { - old_start: h.old_start(), - old_lines: h.old_lines(), - new_start: h.new_start(), - new_lines: h.new_lines(), - } - } + fn from(h: DiffHunk) -> Self { + Self { + old_start: h.old_start(), + old_lines: h.old_lines(), + new_start: h.new_start(), + new_lines: h.new_lines(), + } + } } /// single diff hunk #[derive(Default, Clone, Hash, Debug)] pub struct Hunk { - /// hash of the hunk header - pub header_hash: u64, - /// list of `DiffLine`s - pub lines: Vec, + /// hash of the hunk header + pub header_hash: u64, + /// list of `DiffLine`s + pub lines: Vec, } /// collection of hunks, sum of all diff lines #[derive(Default, Clone, Hash, Debug)] pub struct FileDiff { - /// list of hunks - pub hunks: Vec, - /// lines total summed up over hunks - pub lines: usize, - /// - pub untracked: bool, - /// old and new file size in bytes - pub sizes: (u64, u64), - /// size delta in bytes - pub size_delta: i64, + /// list of hunks + pub hunks: Vec, + /// lines total summed up over hunks + pub lines: usize, + /// + pub untracked: bool, + /// old and new file size in bytes + pub sizes: (u64, u64), + /// size delta in bytes + pub size_delta: i64, } pub(crate) fn get_diff_raw<'a>( - repo: &'a Repository, - p: &str, - stage: bool, - reverse: bool, - context: Option, + repo: &'a Repository, + p: &str, + stage: bool, + reverse: bool, + context: Option, ) -> Result> { - // scope_time!("get_diff_raw"); + // scope_time!("get_diff_raw"); - let mut opt = DiffOptions::new(); - if let Some(context) = context { - opt.context_lines(context); - } - opt.pathspec(p); - opt.reverse(reverse); + let mut opt = DiffOptions::new(); + if let Some(context) = context { + opt.context_lines(context); + } + opt.pathspec(p); + opt.reverse(reverse); - let diff = if stage { - // diff against head - if let Ok(id) = get_head_repo(repo) { - let parent = repo.find_commit(id.into())?; + let diff = if stage { + // diff against head + if let Ok(id) = get_head_repo(repo) { + let parent = repo.find_commit(id.into())?; - let tree = parent.tree()?; - repo.diff_tree_to_index( - Some(&tree), - Some(&repo.index()?), - Some(&mut opt), - )? - } else { - repo.diff_tree_to_index( - None, - Some(&repo.index()?), - Some(&mut opt), - )? - } - } else { - opt.include_untracked(true); - opt.recurse_untracked_dirs(true); - repo.diff_index_to_workdir(None, Some(&mut opt))? - }; + let tree = parent.tree()?; + repo.diff_tree_to_index( + Some(&tree), + Some(&repo.index()?), + Some(&mut opt), + )? + } else { + repo.diff_tree_to_index( + None, + Some(&repo.index()?), + Some(&mut opt), + )? + } + } else { + opt.include_untracked(true); + opt.recurse_untracked_dirs(true); + repo.diff_index_to_workdir(None, Some(&mut opt))? + }; - Ok(diff) + Ok(diff) } /// returns diff of a specific file either in `stage` or workdir pub fn get_diff( - repo_path: &str, - p: &str, - stage: bool, + repo_path: &str, + p: &str, + stage: bool, ) -> Result { - scope_time!("get_diff"); + scope_time!("get_diff"); - let repo = utils::repo(repo_path)?; - let work_dir = work_dir(&repo)?; - let diff = get_diff_raw(&repo, p, stage, false, None)?; + let repo = utils::repo(repo_path)?; + let work_dir = work_dir(&repo)?; + let diff = get_diff_raw(&repo, p, stage, false, None)?; - raw_diff_to_file_diff(&diff, work_dir) + raw_diff_to_file_diff(&diff, work_dir) } /// returns diff of a specific file inside a commit /// see `get_commit_diff` pub fn get_diff_commit( - repo_path: &str, - id: CommitId, - p: String, + repo_path: &str, + id: CommitId, + p: String, ) -> Result { - scope_time!("get_diff_commit"); + scope_time!("get_diff_commit"); - let repo = utils::repo(repo_path)?; - let work_dir = work_dir(&repo)?; - let diff = get_commit_diff(&repo, id, Some(p))?; + let repo = utils::repo(repo_path)?; + let work_dir = work_dir(&repo)?; + let diff = get_commit_diff(&repo, id, Some(p))?; - raw_diff_to_file_diff(&diff, work_dir) + raw_diff_to_file_diff(&diff, work_dir) } /// //TODO: refactor into helper type with the inline closures as dedicated functions #[allow(clippy::too_many_lines)] fn raw_diff_to_file_diff<'a>( - diff: &'a Diff, - work_dir: &Path, + diff: &'a Diff, + work_dir: &Path, ) -> Result { - let res = Rc::new(RefCell::new(FileDiff::default())); - { - let mut current_lines = Vec::new(); - let mut current_hunk: Option = None; + let res = Rc::new(RefCell::new(FileDiff::default())); + { + let mut current_lines = Vec::new(); + let mut current_hunk: Option = None; - let res_cell = Rc::clone(&res); - let adder = move |header: &HunkHeader, - lines: &Vec| { - let mut res = res_cell.borrow_mut(); - res.hunks.push(Hunk { - header_hash: hash(header), - lines: lines.clone(), - }); - res.lines += lines.len(); - }; + let res_cell = Rc::clone(&res); + let adder = move |header: &HunkHeader, + lines: &Vec| { + let mut res = res_cell.borrow_mut(); + res.hunks.push(Hunk { + header_hash: hash(header), + lines: lines.clone(), + }); + res.lines += lines.len(); + }; - let res_cell = Rc::clone(&res); - let mut put = |delta: DiffDelta, - hunk: Option, - line: git2::DiffLine| { - { - let mut res = res_cell.borrow_mut(); - res.sizes = ( - delta.old_file().size(), - delta.new_file().size(), - ); - //TODO: use try_conv - res.size_delta = (i64::conv(res.sizes.1)) - .saturating_sub(i64::conv(res.sizes.0)); - } - if let Some(hunk) = hunk { - let hunk_header = HunkHeader::from(hunk); + let res_cell = Rc::clone(&res); + let mut put = |delta: DiffDelta, + hunk: Option, + line: git2::DiffLine| { + { + let mut res = res_cell.borrow_mut(); + res.sizes = ( + delta.old_file().size(), + delta.new_file().size(), + ); + //TODO: use try_conv + res.size_delta = (i64::conv(res.sizes.1)) + .saturating_sub(i64::conv(res.sizes.0)); + } + if let Some(hunk) = hunk { + let hunk_header = HunkHeader::from(hunk); - match current_hunk { - None => current_hunk = Some(hunk_header), - Some(h) => { - if h != hunk_header { - adder(&h, ¤t_lines); - current_lines.clear(); - current_hunk = Some(hunk_header); - } - } - } + match current_hunk { + None => current_hunk = Some(hunk_header), + Some(h) => { + if h != hunk_header { + adder(&h, ¤t_lines); + current_lines.clear(); + current_hunk = Some(hunk_header); + } + } + } - let diff_line = DiffLine { - position: DiffLinePosition::from(&line), - content: String::from_utf8_lossy(line.content()) - .to_string(), - line_type: line.origin_value().into(), - }; + let diff_line = DiffLine { + position: DiffLinePosition::from(&line), + content: String::from_utf8_lossy(line.content()) + .to_string(), + line_type: line.origin_value().into(), + }; - current_lines.push(diff_line); - } - }; + current_lines.push(diff_line); + } + }; - let new_file_diff = if diff.deltas().len() == 1 { - if let Some(delta) = diff.deltas().next() { - if delta.status() == Delta::Untracked { - let relative_path = - delta.new_file().path().ok_or_else(|| { - Error::Generic( - "new file path is unspecified." - .to_string(), - ) - })?; + let new_file_diff = if diff.deltas().len() == 1 { + if let Some(delta) = diff.deltas().next() { + if delta.status() == Delta::Untracked { + let relative_path = + delta.new_file().path().ok_or_else(|| { + Error::Generic( + "new file path is unspecified." + .to_string(), + ) + })?; - let newfile_path = work_dir.join(relative_path); + let newfile_path = work_dir.join(relative_path); - if let Some(newfile_content) = - new_file_content(&newfile_path) - { - let mut patch = Patch::from_buffers( - &[], - None, - newfile_content.as_slice(), - Some(&newfile_path), - None, - )?; + if let Some(newfile_content) = + new_file_content(&newfile_path) + { + let mut patch = Patch::from_buffers( + &[], + None, + newfile_content.as_slice(), + Some(&newfile_path), + None, + )?; - patch - .print(&mut |delta, hunk:Option, line: git2::DiffLine| { - put(delta,hunk,line); - true - })?; + patch.print( + &mut |delta, + hunk: Option, + line: git2::DiffLine| { + put(delta, hunk, line); + true + }, + )?; - true - } else { - false - } - } else { - false - } - } else { - false - } - } else { - false - }; + true + } else { + false + } + } else { + false + } + } else { + false + } + } else { + false + }; - if !new_file_diff { - diff.print( - DiffFormat::Patch, - move |delta, hunk, line: git2::DiffLine| { - put(delta, hunk, line); - true - }, - )?; - } + if !new_file_diff { + diff.print( + DiffFormat::Patch, + move |delta, hunk, line: git2::DiffLine| { + put(delta, hunk, line); + true + }, + )?; + } - if !current_lines.is_empty() { - adder( - ¤t_hunk.map_or_else( - || Err(Error::Generic("invalid hunk".to_owned())), - Ok, - )?, - ¤t_lines, - ); - } + if !current_lines.is_empty() { + adder( + ¤t_hunk.map_or_else( + || Err(Error::Generic("invalid hunk".to_owned())), + Ok, + )?, + ¤t_lines, + ); + } - if new_file_diff { - res.borrow_mut().untracked = true; - } - } - let res = Rc::try_unwrap(res) - .map_err(|_| Error::Generic("rc unwrap error".to_owned()))?; - Ok(res.into_inner()) + if new_file_diff { + res.borrow_mut().untracked = true; + } + } + let res = Rc::try_unwrap(res) + .map_err(|_| Error::Generic("rc unwrap error".to_owned()))?; + Ok(res.into_inner()) } fn new_file_content(path: &Path) -> Option> { - if let Ok(meta) = fs::symlink_metadata(path) { - if meta.file_type().is_symlink() { - if let Ok(path) = fs::read_link(path) { - return Some( - path.to_str()?.to_string().as_bytes().into(), - ); - } - } else if !meta.file_type().is_dir() { - if let Ok(content) = fs::read(path) { - return Some(content); - } - } - } + if let Ok(meta) = fs::symlink_metadata(path) { + if meta.file_type().is_symlink() { + if let Ok(path) = fs::read_link(path) { + return Some( + path.to_str()?.to_string().as_bytes().into(), + ); + } + } else if !meta.file_type().is_dir() { + if let Ok(content) = fs::read(path) { + return Some(content); + } + } + } - None + None } #[cfg(test)] mod tests { - use super::{get_diff, get_diff_commit}; - use crate::error::Result; - use crate::sync::{ - commit, stage_add_file, - status::{get_status, StatusType}, - tests::{get_statuses, repo_init, repo_init_empty}, - }; - use std::{ - fs::{self, File}, - io::Write, - path::Path, - }; + use super::{get_diff, get_diff_commit}; + use crate::error::Result; + use crate::sync::{ + commit, stage_add_file, + status::{get_status, StatusType}, + tests::{get_statuses, repo_init, repo_init_empty}, + }; + use std::{ + fs::{self, File}, + io::Write, + path::Path, + }; - #[test] - fn test_untracked_subfolder() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_untracked_subfolder() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar.txt")) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); + fs::create_dir(&root.join("foo")).unwrap(); + File::create(&root.join("foo/bar.txt")) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - let diff = get_diff(repo_path, "foo/bar.txt", false).unwrap(); + let diff = get_diff(repo_path, "foo/bar.txt", false).unwrap(); - assert_eq!(diff.hunks.len(), 1); - assert_eq!(diff.hunks[0].lines[1].content, "test\n"); - } + assert_eq!(diff.hunks.len(), 1); + assert_eq!(diff.hunks[0].lines[1].content, "test\n"); + } - #[test] - fn test_empty_repo() { - let file_path = Path::new("foo.txt"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_empty_repo() { + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - File::create(&root.join(file_path)) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); + File::create(&root.join(file_path)) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - assert_eq!(get_statuses(repo_path), (0, 1)); + assert_eq!(get_statuses(repo_path), (0, 1)); - let diff = - get_diff(repo_path, file_path.to_str().unwrap(), true) - .unwrap(); + let diff = + get_diff(repo_path, file_path.to_str().unwrap(), true) + .unwrap(); - assert_eq!(diff.hunks.len(), 1); - } + assert_eq!(diff.hunks.len(), 1); + } - static HUNK_A: &str = r" + static HUNK_A: &str = r" 1 start 2 3 @@ -429,7 +432,7 @@ mod tests { 0 1 end"; - static HUNK_B: &str = r" + static HUNK_B: &str = r" 1 start 2 newa 3 @@ -442,145 +445,145 @@ mod tests { 0 newb 1 end"; - #[test] - fn test_hunks() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_hunks() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - let file_path = root.join("bar.txt"); + let file_path = root.join("bar.txt"); - { - File::create(&file_path) - .unwrap() - .write_all(HUNK_A.as_bytes()) - .unwrap(); - } + { + File::create(&file_path) + .unwrap() + .write_all(HUNK_A.as_bytes()) + .unwrap(); + } - let res = - get_status(repo_path, StatusType::WorkingDir).unwrap(); - assert_eq!(res.len(), 1); - assert_eq!(res[0].path, "bar.txt"); + let res = + get_status(repo_path, StatusType::WorkingDir).unwrap(); + assert_eq!(res.len(), 1); + assert_eq!(res[0].path, "bar.txt"); - stage_add_file(repo_path, Path::new("bar.txt")).unwrap(); - assert_eq!(get_statuses(repo_path), (0, 1)); + stage_add_file(repo_path, Path::new("bar.txt")).unwrap(); + assert_eq!(get_statuses(repo_path), (0, 1)); - // overwrite with next content - { - File::create(&file_path) - .unwrap() - .write_all(HUNK_B.as_bytes()) - .unwrap(); - } + // overwrite with next content + { + File::create(&file_path) + .unwrap() + .write_all(HUNK_B.as_bytes()) + .unwrap(); + } - assert_eq!(get_statuses(repo_path), (1, 1)); + assert_eq!(get_statuses(repo_path), (1, 1)); - let res = get_diff(repo_path, "bar.txt", false).unwrap(); + let res = get_diff(repo_path, "bar.txt", false).unwrap(); - assert_eq!(res.hunks.len(), 2) - } + assert_eq!(res.hunks.len(), 2) + } - #[test] - fn test_diff_newfile_in_sub_dir_current_dir() { - let file_path = Path::new("foo/foo.txt"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); + #[test] + fn test_diff_newfile_in_sub_dir_current_dir() { + let file_path = Path::new("foo/foo.txt"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); - let sub_path = root.join("foo/"); + let sub_path = root.join("foo/"); - fs::create_dir_all(&sub_path).unwrap(); - File::create(&root.join(file_path)) - .unwrap() - .write_all(b"test") - .unwrap(); + fs::create_dir_all(&sub_path).unwrap(); + File::create(&root.join(file_path)) + .unwrap() + .write_all(b"test") + .unwrap(); - let diff = get_diff( - sub_path.to_str().unwrap(), - file_path.to_str().unwrap(), - false, - ) - .unwrap(); + let diff = get_diff( + sub_path.to_str().unwrap(), + file_path.to_str().unwrap(), + false, + ) + .unwrap(); - assert_eq!(diff.hunks[0].lines[1].content, "test"); - } + assert_eq!(diff.hunks[0].lines[1].content, "test"); + } - #[test] - fn test_diff_delta_size() -> Result<()> { - let file_path = Path::new("bar"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_diff_delta_size() -> Result<()> { + let file_path = Path::new("bar"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"\x00")?; + File::create(&root.join(file_path))?.write_all(b"\x00")?; - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - commit(repo_path, "commit").unwrap(); + commit(repo_path, "commit").unwrap(); - File::create(&root.join(file_path))? - .write_all(b"\x00\x02")?; + File::create(&root.join(file_path))? + .write_all(b"\x00\x02")?; - let diff = - get_diff(repo_path, file_path.to_str().unwrap(), false) - .unwrap(); + let diff = + get_diff(repo_path, file_path.to_str().unwrap(), false) + .unwrap(); - dbg!(&diff); - assert_eq!(diff.sizes, (1, 2)); - assert_eq!(diff.size_delta, 1); + dbg!(&diff); + assert_eq!(diff.sizes, (1, 2)); + assert_eq!(diff.size_delta, 1); - Ok(()) - } + Ok(()) + } - #[test] - fn test_binary_diff_delta_size_untracked() -> Result<()> { - let file_path = Path::new("bar"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_binary_diff_delta_size_untracked() -> Result<()> { + let file_path = Path::new("bar"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))? - .write_all(b"\x00\xc7")?; + File::create(&root.join(file_path))? + .write_all(b"\x00\xc7")?; - let diff = - get_diff(repo_path, file_path.to_str().unwrap(), false) - .unwrap(); + let diff = + get_diff(repo_path, file_path.to_str().unwrap(), false) + .unwrap(); - dbg!(&diff); - assert_eq!(diff.sizes, (0, 2)); - assert_eq!(diff.size_delta, 2); + dbg!(&diff); + assert_eq!(diff.sizes, (0, 2)); + assert_eq!(diff.size_delta, 2); - Ok(()) - } + Ok(()) + } - #[test] - fn test_diff_delta_size_commit() -> Result<()> { - let file_path = Path::new("bar"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_diff_delta_size_commit() -> Result<()> { + let file_path = Path::new("bar"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"\x00")?; + File::create(&root.join(file_path))?.write_all(b"\x00")?; - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - commit(repo_path, "").unwrap(); + commit(repo_path, "").unwrap(); - File::create(&root.join(file_path))? - .write_all(b"\x00\x02")?; + File::create(&root.join(file_path))? + .write_all(b"\x00\x02")?; - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - let id = commit(repo_path, "").unwrap(); + let id = commit(repo_path, "").unwrap(); - let diff = - get_diff_commit(repo_path, id, String::new()).unwrap(); + let diff = + get_diff_commit(repo_path, id, String::new()).unwrap(); - dbg!(&diff); - assert_eq!(diff.sizes, (1, 2)); - assert_eq!(diff.size_delta, 1); + dbg!(&diff); + assert_eq!(diff.sizes, (1, 2)); + assert_eq!(diff.size_delta, 1); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index ac6d567a..fc076f59 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -2,10 +2,10 @@ use super::utils::{repo, work_dir}; use crate::error::{Error, Result}; use scopetime::scope_time; use std::{ - fs::File, - io::{Read, Write}, - path::Path, - process::Command, + fs::File, + io::{Read, Write}, + path::Path, + process::Command, }; const HOOK_POST_COMMIT: &str = ".git/hooks/post-commit"; @@ -18,372 +18,372 @@ const HOOK_COMMIT_MSG_TEMP_FILE: &str = ".git/COMMIT_EDITMSG"; /// the commit message at `.git/COMMIT_EDITMSG` and pass it's relative path as the only /// parameter to the hook script. pub fn hooks_commit_msg( - repo_path: &str, - msg: &mut String, + repo_path: &str, + msg: &mut String, ) -> Result { - scope_time!("hooks_commit_msg"); + scope_time!("hooks_commit_msg"); - let work_dir = work_dir_as_string(repo_path)?; + let work_dir = work_dir_as_string(repo_path)?; - if hook_runable(work_dir.as_str(), HOOK_COMMIT_MSG) { - let temp_file = Path::new(work_dir.as_str()) - .join(HOOK_COMMIT_MSG_TEMP_FILE); - File::create(&temp_file)?.write_all(msg.as_bytes())?; + if hook_runable(work_dir.as_str(), HOOK_COMMIT_MSG) { + let temp_file = Path::new(work_dir.as_str()) + .join(HOOK_COMMIT_MSG_TEMP_FILE); + File::create(&temp_file)?.write_all(msg.as_bytes())?; - let res = run_hook( - work_dir.as_str(), - HOOK_COMMIT_MSG, - &[HOOK_COMMIT_MSG_TEMP_FILE], - )?; + let res = run_hook( + work_dir.as_str(), + HOOK_COMMIT_MSG, + &[HOOK_COMMIT_MSG_TEMP_FILE], + )?; - // load possibly altered msg - msg.clear(); - File::open(temp_file)?.read_to_string(msg)?; + // load possibly altered msg + msg.clear(); + File::open(temp_file)?.read_to_string(msg)?; - Ok(res) - } else { - Ok(HookResult::Ok) - } + Ok(res) + } else { + Ok(HookResult::Ok) + } } /// this hook is documented here /// pub fn hooks_pre_commit(repo_path: &str) -> Result { - scope_time!("hooks_pre_commit"); + scope_time!("hooks_pre_commit"); - let work_dir = work_dir_as_string(repo_path)?; + let work_dir = work_dir_as_string(repo_path)?; - if hook_runable(work_dir.as_str(), HOOK_PRE_COMMIT) { - Ok(run_hook(work_dir.as_str(), HOOK_PRE_COMMIT, &[])?) - } else { - Ok(HookResult::Ok) - } + if hook_runable(work_dir.as_str(), HOOK_PRE_COMMIT) { + Ok(run_hook(work_dir.as_str(), HOOK_PRE_COMMIT, &[])?) + } else { + Ok(HookResult::Ok) + } } /// pub fn hooks_post_commit(repo_path: &str) -> Result { - scope_time!("hooks_post_commit"); + scope_time!("hooks_post_commit"); - let work_dir = work_dir_as_string(repo_path)?; - let work_dir_str = work_dir.as_str(); + let work_dir = work_dir_as_string(repo_path)?; + let work_dir_str = work_dir.as_str(); - if hook_runable(work_dir_str, HOOK_POST_COMMIT) { - Ok(run_hook(work_dir_str, HOOK_POST_COMMIT, &[])?) - } else { - Ok(HookResult::Ok) - } + if hook_runable(work_dir_str, HOOK_POST_COMMIT) { + Ok(run_hook(work_dir_str, HOOK_POST_COMMIT, &[])?) + } else { + Ok(HookResult::Ok) + } } fn work_dir_as_string(repo_path: &str) -> Result { - let repo = repo(repo_path)?; - work_dir(&repo)? - .to_str() - .map(std::string::ToString::to_string) - .ok_or_else(|| { - Error::Generic( - "workdir contains invalid utf8".to_string(), - ) - }) + let repo = repo(repo_path)?; + work_dir(&repo)? + .to_str() + .map(std::string::ToString::to_string) + .ok_or_else(|| { + Error::Generic( + "workdir contains invalid utf8".to_string(), + ) + }) } fn hook_runable(path: &str, hook: &str) -> bool { - let path = Path::new(path); - let path = path.join(hook); + let path = Path::new(path); + let path = path.join(hook); - path.exists() && is_executable(&path) + path.exists() && is_executable(&path) } /// #[derive(Debug, PartialEq)] pub enum HookResult { - /// Everything went fine - Ok, - /// Hook returned error - NotOk(String), + /// Everything went fine + Ok, + /// Hook returned error + NotOk(String), } /// this function calls hook scripts based on conventions documented here /// see fn run_hook( - path: &str, - hook_script: &str, - args: &[&str], + path: &str, + hook_script: &str, + args: &[&str], ) -> Result { - let arg_str = format!("{} {}", hook_script, args.join(" ")); - let bash_args = vec!["-c".to_string(), arg_str]; + let arg_str = format!("{} {}", hook_script, args.join(" ")); + let bash_args = vec!["-c".to_string(), arg_str]; - let output = Command::new("bash") - .args(bash_args) - .current_dir(path) - // This call forces Command to handle the Path environment correctly on windows, - // the specific env set here does not matter - // see https://github.com/rust-lang/rust/issues/37519 - .env( - "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", - "FixPathHandlingOnWindows", - ) - .output()?; + let output = Command::new("bash") + .args(bash_args) + .current_dir(path) + // This call forces Command to handle the Path environment correctly on windows, + // the specific env set here does not matter + // see https://github.com/rust-lang/rust/issues/37519 + .env( + "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", + "FixPathHandlingOnWindows", + ) + .output()?; - if output.status.success() { - Ok(HookResult::Ok) - } else { - let err = String::from_utf8_lossy(&output.stderr); - let out = String::from_utf8_lossy(&output.stdout); - let formatted = format!("{}{}", out, err); + if output.status.success() { + Ok(HookResult::Ok) + } else { + let err = String::from_utf8_lossy(&output.stderr); + let out = String::from_utf8_lossy(&output.stdout); + let formatted = format!("{}{}", out, err); - Ok(HookResult::NotOk(formatted)) - } + Ok(HookResult::NotOk(formatted)) + } } #[cfg(not(windows))] fn is_executable(path: &Path) -> bool { - use std::os::unix::fs::PermissionsExt; - let metadata = match path.metadata() { - Ok(metadata) => metadata, - Err(_) => return false, - }; + use std::os::unix::fs::PermissionsExt; + let metadata = match path.metadata() { + Ok(metadata) => metadata, + Err(_) => return false, + }; - let permissions = metadata.permissions(); - permissions.mode() & 0o111 != 0 + let permissions = metadata.permissions(); + permissions.mode() & 0o111 != 0 } #[cfg(windows)] /// windows does not consider bash scripts to be executable so we consider everything /// to be executable (which is not far from the truth for windows platform.) const fn is_executable(_: &Path) -> bool { - true + true } #[cfg(test)] mod tests { - use super::*; - use crate::sync::tests::repo_init; - use std::fs::{self, File}; + use super::*; + use crate::sync::tests::repo_init; + use std::fs::{self, File}; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let mut msg = String::from("test"); - let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); + let mut msg = String::from("test"); + let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); - assert_eq!(res, HookResult::Ok); + assert_eq!(res, HookResult::Ok); - let res = hooks_post_commit(repo_path).unwrap(); + let res = hooks_post_commit(repo_path).unwrap(); - assert_eq!(res, HookResult::Ok); - } + assert_eq!(res, HookResult::Ok); + } - fn create_hook(path: &Path, hook_path: &str, hook_script: &[u8]) { - File::create(&path.join(hook_path)) - .unwrap() - .write_all(hook_script) - .unwrap(); + fn create_hook(path: &Path, hook_path: &str, hook_script: &[u8]) { + File::create(&path.join(hook_path)) + .unwrap() + .write_all(hook_script) + .unwrap(); - #[cfg(not(windows))] - { - Command::new("chmod") - .args(&["+x", hook_path]) - .current_dir(path) - .output() - .unwrap(); - } - } + #[cfg(not(windows))] + { + Command::new("chmod") + .args(&["+x", hook_path]) + .current_dir(path) + .output() + .unwrap(); + } + } - #[test] - fn test_hooks_commit_msg_ok() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_hooks_commit_msg_ok() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh exit 0 "; - create_hook(root, HOOK_COMMIT_MSG, hook); + create_hook(root, HOOK_COMMIT_MSG, hook); - let mut msg = String::from("test"); - let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); + let mut msg = String::from("test"); + let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); - assert_eq!(res, HookResult::Ok); + assert_eq!(res, HookResult::Ok); - assert_eq!(msg, String::from("test")); - } + assert_eq!(msg, String::from("test")); + } - #[test] - fn test_pre_commit_sh() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_pre_commit_sh() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh exit 0 "; - create_hook(root, HOOK_PRE_COMMIT, hook); - let res = hooks_pre_commit(repo_path).unwrap(); - assert_eq!(res, HookResult::Ok); - } + create_hook(root, HOOK_PRE_COMMIT, hook); + let res = hooks_pre_commit(repo_path).unwrap(); + assert_eq!(res, HookResult::Ok); + } - #[test] - fn test_pre_commit_fail_sh() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_pre_commit_fail_sh() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh echo 'rejected' exit 1 "; - create_hook(root, HOOK_PRE_COMMIT, hook); - let res = hooks_pre_commit(repo_path).unwrap(); - assert!(res != HookResult::Ok); - } + create_hook(root, HOOK_PRE_COMMIT, hook); + let res = hooks_pre_commit(repo_path).unwrap(); + assert!(res != HookResult::Ok); + } - #[test] - fn test_pre_commit_py() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_pre_commit_py() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - // mirror how python pre-commmit sets itself up - #[cfg(not(windows))] - let hook = b"#!/usr/bin/env python + // mirror how python pre-commmit sets itself up + #[cfg(not(windows))] + let hook = b"#!/usr/bin/env python import sys sys.exit(0) "; - #[cfg(windows)] - let hook = b"#!/bin/env python.exe + #[cfg(windows)] + let hook = b"#!/bin/env python.exe import sys sys.exit(0) "; - create_hook(root, HOOK_PRE_COMMIT, hook); - let res = hooks_pre_commit(repo_path).unwrap(); - assert_eq!(res, HookResult::Ok); - } + create_hook(root, HOOK_PRE_COMMIT, hook); + let res = hooks_pre_commit(repo_path).unwrap(); + assert_eq!(res, HookResult::Ok); + } - #[test] - fn test_pre_commit_fail_py() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_pre_commit_fail_py() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - // mirror how python pre-commmit sets itself up - #[cfg(not(windows))] - let hook = b"#!/usr/bin/env python + // mirror how python pre-commmit sets itself up + #[cfg(not(windows))] + let hook = b"#!/usr/bin/env python import sys sys.exit(1) "; - #[cfg(windows)] - let hook = b"#!/bin/env python.exe + #[cfg(windows)] + let hook = b"#!/bin/env python.exe import sys sys.exit(1) "; - create_hook(root, HOOK_PRE_COMMIT, hook); - let res = hooks_pre_commit(repo_path).unwrap(); - assert!(res != HookResult::Ok); - } + create_hook(root, HOOK_PRE_COMMIT, hook); + let res = hooks_pre_commit(repo_path).unwrap(); + assert!(res != HookResult::Ok); + } - #[test] - fn test_hooks_commit_msg_reject() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_hooks_commit_msg_reject() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh echo 'msg' > $1 echo 'rejected' exit 1 "; - create_hook(root, HOOK_COMMIT_MSG, hook); + create_hook(root, HOOK_COMMIT_MSG, hook); - let mut msg = String::from("test"); - let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); + let mut msg = String::from("test"); + let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); - assert_eq!( - res, - HookResult::NotOk(String::from("rejected\n")) - ); + assert_eq!( + res, + HookResult::NotOk(String::from("rejected\n")) + ); - assert_eq!(msg, String::from("msg\n")); - } + assert_eq!(msg, String::from("msg\n")); + } - #[test] - fn test_hooks_commit_msg_reject_in_subfolder() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - // let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_hooks_commit_msg_reject_in_subfolder() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + // let repo_path = root.as_os_str().to_str().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh echo 'msg' > $1 echo 'rejected' exit 1 "; - create_hook(root, HOOK_COMMIT_MSG, hook); + create_hook(root, HOOK_COMMIT_MSG, hook); - let subfolder = root.join("foo/"); - fs::create_dir_all(&subfolder).unwrap(); + let subfolder = root.join("foo/"); + fs::create_dir_all(&subfolder).unwrap(); - let mut msg = String::from("test"); - let res = - hooks_commit_msg(subfolder.to_str().unwrap(), &mut msg) - .unwrap(); + let mut msg = String::from("test"); + let res = + hooks_commit_msg(subfolder.to_str().unwrap(), &mut msg) + .unwrap(); - assert_eq!( - res, - HookResult::NotOk(String::from("rejected\n")) - ); + assert_eq!( + res, + HookResult::NotOk(String::from("rejected\n")) + ); - assert_eq!(msg, String::from("msg\n")); - } + assert_eq!(msg, String::from("msg\n")); + } - #[test] - fn test_commit_msg_no_block_but_alter() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_commit_msg_no_block_but_alter() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh echo 'msg' > $1 exit 0 "; - create_hook(root, HOOK_COMMIT_MSG, hook); + create_hook(root, HOOK_COMMIT_MSG, hook); - let mut msg = String::from("test"); - let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); + let mut msg = String::from("test"); + let res = hooks_commit_msg(repo_path, &mut msg).unwrap(); - assert_eq!(res, HookResult::Ok); - assert_eq!(msg, String::from("msg\n")); - } + assert_eq!(res, HookResult::Ok); + assert_eq!(msg, String::from("msg\n")); + } - #[test] - fn test_post_commit_hook_reject_in_subfolder() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); + #[test] + fn test_post_commit_hook_reject_in_subfolder() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); - let hook = b"#!/bin/sh + let hook = b"#!/bin/sh echo 'rejected' exit 1 "; - create_hook(root, HOOK_POST_COMMIT, hook); + create_hook(root, HOOK_POST_COMMIT, hook); - let subfolder = root.join("foo/"); - fs::create_dir_all(&subfolder).unwrap(); + let subfolder = root.join("foo/"); + fs::create_dir_all(&subfolder).unwrap(); - let res = - hooks_post_commit(subfolder.to_str().unwrap()).unwrap(); + let res = + hooks_post_commit(subfolder.to_str().unwrap()).unwrap(); - assert_eq!( - res, - HookResult::NotOk(String::from("rejected\n")) - ); - } + assert_eq!( + res, + HookResult::NotOk(String::from("rejected\n")) + ); + } } diff --git a/asyncgit/src/sync/hunks.rs b/asyncgit/src/sync/hunks.rs index fd77d772..9cc9b2f0 100644 --- a/asyncgit/src/sync/hunks.rs +++ b/asyncgit/src/sync/hunks.rs @@ -1,187 +1,187 @@ use super::{ - diff::{get_diff_raw, HunkHeader}, - utils::repo, + diff::{get_diff_raw, HunkHeader}, + utils::repo, }; use crate::{ - error::{Error, Result}, - hash, + error::{Error, Result}, + hash, }; use git2::{ApplyLocation, ApplyOptions, Diff}; use scopetime::scope_time; /// pub fn stage_hunk( - repo_path: &str, - file_path: &str, - hunk_hash: u64, + repo_path: &str, + file_path: &str, + hunk_hash: u64, ) -> Result<()> { - scope_time!("stage_hunk"); + scope_time!("stage_hunk"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let diff = get_diff_raw(&repo, file_path, false, false, None)?; + let diff = get_diff_raw(&repo, file_path, false, false, None)?; - let mut opt = ApplyOptions::new(); - opt.hunk_callback(|hunk| { - hunk.map_or(false, |hunk| { - let header = HunkHeader::from(hunk); - hash(&header) == hunk_hash - }) - }); + let mut opt = ApplyOptions::new(); + opt.hunk_callback(|hunk| { + hunk.map_or(false, |hunk| { + let header = HunkHeader::from(hunk); + hash(&header) == hunk_hash + }) + }); - repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?; + repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?; - Ok(()) + Ok(()) } /// this will fail for an all untracked file pub fn reset_hunk( - repo_path: &str, - file_path: &str, - hunk_hash: u64, + repo_path: &str, + file_path: &str, + hunk_hash: u64, ) -> Result<()> { - scope_time!("reset_hunk"); + scope_time!("reset_hunk"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let diff = get_diff_raw(&repo, file_path, false, false, None)?; + let diff = get_diff_raw(&repo, file_path, false, false, None)?; - let hunk_index = find_hunk_index(&diff, hunk_hash); - if let Some(hunk_index) = hunk_index { - let mut hunk_idx = 0; - let mut opt = ApplyOptions::new(); - opt.hunk_callback(|_hunk| { - let res = hunk_idx == hunk_index; - hunk_idx += 1; - res - }); + let hunk_index = find_hunk_index(&diff, hunk_hash); + if let Some(hunk_index) = hunk_index { + let mut hunk_idx = 0; + let mut opt = ApplyOptions::new(); + opt.hunk_callback(|_hunk| { + let res = hunk_idx == hunk_index; + hunk_idx += 1; + res + }); - let diff = get_diff_raw(&repo, file_path, false, true, None)?; + let diff = get_diff_raw(&repo, file_path, false, true, None)?; - repo.apply(&diff, ApplyLocation::WorkDir, Some(&mut opt))?; + repo.apply(&diff, ApplyLocation::WorkDir, Some(&mut opt))?; - Ok(()) - } else { - Err(Error::Generic("hunk not found".to_string())) - } + Ok(()) + } else { + Err(Error::Generic("hunk not found".to_string())) + } } fn find_hunk_index(diff: &Diff, hunk_hash: u64) -> Option { - let mut result = None; + let mut result = None; - let mut hunk_count = 0; + let mut hunk_count = 0; - let foreach_result = diff.foreach( - &mut |_, _| true, - None, - Some(&mut |_, hunk| { - let header = HunkHeader::from(hunk); - if hash(&header) == hunk_hash { - result = Some(hunk_count); - } - hunk_count += 1; - true - }), - None, - ); + let foreach_result = diff.foreach( + &mut |_, _| true, + None, + Some(&mut |_, hunk| { + let header = HunkHeader::from(hunk); + if hash(&header) == hunk_hash { + result = Some(hunk_count); + } + hunk_count += 1; + true + }), + None, + ); - if foreach_result.is_ok() { - result - } else { - None - } + if foreach_result.is_ok() { + result + } else { + None + } } /// pub fn unstage_hunk( - repo_path: &str, - file_path: &str, - hunk_hash: u64, + repo_path: &str, + file_path: &str, + hunk_hash: u64, ) -> Result { - scope_time!("revert_hunk"); + scope_time!("revert_hunk"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let diff = get_diff_raw(&repo, file_path, true, false, None)?; - let diff_count_positive = diff.deltas().len(); + let diff = get_diff_raw(&repo, file_path, true, false, None)?; + let diff_count_positive = diff.deltas().len(); - let hunk_index = find_hunk_index(&diff, hunk_hash); - let hunk_index = hunk_index.map_or_else( - || Err(Error::Generic("hunk not found".to_string())), - Ok, - )?; + let hunk_index = find_hunk_index(&diff, hunk_hash); + let hunk_index = hunk_index.map_or_else( + || Err(Error::Generic("hunk not found".to_string())), + Ok, + )?; - let diff = get_diff_raw(&repo, file_path, true, true, None)?; + let diff = get_diff_raw(&repo, file_path, true, true, None)?; - if diff.deltas().len() != diff_count_positive { - return Err(Error::Generic(format!( - "hunk error: {}!={}", - diff.deltas().len(), - diff_count_positive - ))); - } + if diff.deltas().len() != diff_count_positive { + return Err(Error::Generic(format!( + "hunk error: {}!={}", + diff.deltas().len(), + diff_count_positive + ))); + } - let mut count = 0; - { - let mut hunk_idx = 0; - let mut opt = ApplyOptions::new(); - opt.hunk_callback(|_hunk| { - let res = if hunk_idx == hunk_index { - count += 1; - true - } else { - false - }; + let mut count = 0; + { + let mut hunk_idx = 0; + let mut opt = ApplyOptions::new(); + opt.hunk_callback(|_hunk| { + let res = if hunk_idx == hunk_index { + count += 1; + true + } else { + false + }; - hunk_idx += 1; + hunk_idx += 1; - res - }); + res + }); - repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?; - } + repo.apply(&diff, ApplyLocation::Index, Some(&mut opt))?; + } - Ok(count == 1) + Ok(count == 1) } #[cfg(test)] mod tests { - use super::*; - use crate::{ - error::Result, - sync::{diff::get_diff, tests::repo_init_empty}, - }; - use std::{ - fs::{self, File}, - io::Write, - path::Path, - }; + use super::*; + use crate::{ + error::Result, + sync::{diff::get_diff, tests::repo_init_empty}, + }; + use std::{ + fs::{self, File}, + io::Write, + path::Path, + }; - #[test] - fn reset_untracked_file_which_will_not_find_hunk() -> Result<()> { - let file_path = Path::new("foo/foo.txt"); - let (_td, repo) = repo_init_empty()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn reset_untracked_file_which_will_not_find_hunk() -> Result<()> { + let file_path = Path::new("foo/foo.txt"); + let (_td, repo) = repo_init_empty()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let sub_path = root.join("foo/"); + let sub_path = root.join("foo/"); - fs::create_dir_all(&sub_path)?; - File::create(&root.join(file_path))?.write_all(b"test")?; + fs::create_dir_all(&sub_path)?; + File::create(&root.join(file_path))?.write_all(b"test")?; - let diff = get_diff( - sub_path.to_str().unwrap(), - file_path.to_str().unwrap(), - false, - )?; + let diff = get_diff( + sub_path.to_str().unwrap(), + file_path.to_str().unwrap(), + false, + )?; - assert!(reset_hunk( - repo_path, - file_path.to_str().unwrap(), - diff.hunks[0].header_hash, - ) - .is_err()); + assert!(reset_hunk( + repo_path, + file_path.to_str().unwrap(), + diff.hunks[0].header_hash, + ) + .is_err()); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/ignore.rs b/asyncgit/src/sync/ignore.rs index a9552c65..4fee7178 100644 --- a/asyncgit/src/sync/ignore.rs +++ b/asyncgit/src/sync/ignore.rs @@ -2,151 +2,151 @@ use super::utils::{repo, work_dir}; use crate::error::{Error, Result}; use scopetime::scope_time; use std::{ - fs::{File, OpenOptions}, - io::{Read, Seek, SeekFrom, Write}, - path::Path, + fs::{File, OpenOptions}, + io::{Read, Seek, SeekFrom, Write}, + path::Path, }; static GITIGNORE: &str = ".gitignore"; /// add file or path to root ignore file pub fn add_to_ignore( - repo_path: &str, - path_to_ignore: &str, + repo_path: &str, + path_to_ignore: &str, ) -> Result<()> { - scope_time!("add_to_ignore"); + scope_time!("add_to_ignore"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - if Path::new(path_to_ignore).file_name() - == Path::new(GITIGNORE).file_name() - { - return Err(Error::Generic(String::from( - "cannot ignore gitignore", - ))); - } + if Path::new(path_to_ignore).file_name() + == Path::new(GITIGNORE).file_name() + { + return Err(Error::Generic(String::from( + "cannot ignore gitignore", + ))); + } - let ignore_file = work_dir(&repo)?.join(GITIGNORE); + let ignore_file = work_dir(&repo)?.join(GITIGNORE); - let optional_newline = ignore_file.exists() - && !file_ends_with_newline(&ignore_file)?; + let optional_newline = ignore_file.exists() + && !file_ends_with_newline(&ignore_file)?; - let mut file = OpenOptions::new() - .append(true) - .create(true) - .open(ignore_file)?; + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open(ignore_file)?; - writeln!( - file, - "{}{}", - if optional_newline { "\n" } else { "" }, - path_to_ignore - )?; + writeln!( + file, + "{}{}", + if optional_newline { "\n" } else { "" }, + path_to_ignore + )?; - Ok(()) + Ok(()) } fn file_ends_with_newline(file: &Path) -> Result { - let mut file = File::open(file)?; - let size = file.metadata()?.len(); + let mut file = File::open(file)?; + let size = file.metadata()?.len(); - file.seek(SeekFrom::Start(size.saturating_sub(1)))?; - let mut last_char = String::with_capacity(1); - file.read_to_string(&mut last_char)?; + file.seek(SeekFrom::Start(size.saturating_sub(1)))?; + let mut last_char = String::with_capacity(1); + file.read_to_string(&mut last_char)?; - Ok(last_char == "\n") + Ok(last_char == "\n") } #[cfg(test)] mod tests { - use super::*; - use crate::sync::{tests::repo_init, utils::repo_write_file}; - use io::BufRead; - use pretty_assertions::assert_eq; - use std::{fs::File, io, path::Path}; + use super::*; + use crate::sync::{tests::repo_init, utils::repo_write_file}; + use io::BufRead; + use pretty_assertions::assert_eq; + use std::{fs::File, io, path::Path}; - #[test] - fn test_empty() -> Result<()> { - let ignore_file_path = Path::new(".gitignore"); - let file_path = Path::new("foo.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_empty() -> Result<()> { + let ignore_file_path = Path::new(".gitignore"); + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"test")?; + File::create(&root.join(file_path))?.write_all(b"test")?; - assert_eq!(root.join(ignore_file_path).exists(), false); - add_to_ignore(repo_path, file_path.to_str().unwrap())?; - assert_eq!(root.join(ignore_file_path).exists(), true); + assert_eq!(root.join(ignore_file_path).exists(), false); + add_to_ignore(repo_path, file_path.to_str().unwrap())?; + assert_eq!(root.join(ignore_file_path).exists(), true); - Ok(()) - } + Ok(()) + } - fn read_lines

( - filename: P, - ) -> io::Result>> - where - P: AsRef, - { - let file = File::open(filename)?; - Ok(io::BufReader::new(file).lines()) - } + fn read_lines

( + filename: P, + ) -> io::Result>> + where + P: AsRef, + { + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) + } - #[test] - fn test_append() -> Result<()> { - let ignore_file_path = Path::new(".gitignore"); - let file_path = Path::new("foo.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_append() -> Result<()> { + let ignore_file_path = Path::new(".gitignore"); + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"test")?; - File::create(&root.join(ignore_file_path))? - .write_all(b"foo\n")?; + File::create(&root.join(file_path))?.write_all(b"test")?; + File::create(&root.join(ignore_file_path))? + .write_all(b"foo\n")?; - add_to_ignore(repo_path, file_path.to_str().unwrap())?; + add_to_ignore(repo_path, file_path.to_str().unwrap())?; - let mut lines = - read_lines(&root.join(ignore_file_path)).unwrap(); - assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt"); + let mut lines = + read_lines(&root.join(ignore_file_path)).unwrap(); + assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt"); - Ok(()) - } + Ok(()) + } - #[test] - fn test_append_no_newline_at_end() -> Result<()> { - let ignore_file_path = Path::new(".gitignore"); - let file_path = Path::new("foo.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_append_no_newline_at_end() -> Result<()> { + let ignore_file_path = Path::new(".gitignore"); + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"test")?; - File::create(&root.join(ignore_file_path))? - .write_all(b"foo")?; + File::create(&root.join(file_path))?.write_all(b"test")?; + File::create(&root.join(ignore_file_path))? + .write_all(b"foo")?; - add_to_ignore(repo_path, file_path.to_str().unwrap())?; + add_to_ignore(repo_path, file_path.to_str().unwrap())?; - let mut lines = - read_lines(&root.join(ignore_file_path)).unwrap(); - assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt"); + let mut lines = + read_lines(&root.join(ignore_file_path)).unwrap(); + assert_eq!(&lines.nth(1).unwrap().unwrap(), "foo.txt"); - Ok(()) - } + Ok(()) + } - #[test] - fn test_ignore_ignore() { - let ignore_file_path = Path::new(".gitignore"); - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_ignore_ignore() { + let ignore_file_path = Path::new(".gitignore"); + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - repo_write_file(&repo, ".gitignore", "#foo").unwrap(); + repo_write_file(&repo, ".gitignore", "#foo").unwrap(); - let res = add_to_ignore(repo_path, ".gitignore"); - assert!(res.is_err()); + let res = add_to_ignore(repo_path, ".gitignore"); + assert!(res.is_err()); - let lines = read_lines(&root.join(ignore_file_path)).unwrap(); - assert_eq!(lines.count(), 1); - } + let lines = read_lines(&root.join(ignore_file_path)).unwrap(); + assert_eq!(lines.count(), 1); + } } diff --git a/asyncgit/src/sync/logwalker.rs b/asyncgit/src/sync/logwalker.rs index b5caf946..bbc2c2b8 100644 --- a/asyncgit/src/sync/logwalker.rs +++ b/asyncgit/src/sync/logwalker.rs @@ -2,9 +2,9 @@ use super::CommitId; use crate::error::Result; use git2::{Commit, Oid, Repository}; use std::{ - cmp::Ordering, - collections::{BinaryHeap, HashSet}, - sync::Arc, + cmp::Ordering, + collections::{BinaryHeap, HashSet}, + sync::Arc, }; struct TimeOrderedCommit<'a>(Commit<'a>); @@ -12,237 +12,237 @@ struct TimeOrderedCommit<'a>(Commit<'a>); impl<'a> Eq for TimeOrderedCommit<'a> {} impl<'a> PartialEq for TimeOrderedCommit<'a> { - fn eq(&self, other: &Self) -> bool { - self.0.time().eq(&other.0.time()) - } + fn eq(&self, other: &Self) -> bool { + self.0.time().eq(&other.0.time()) + } } impl<'a> PartialOrd for TimeOrderedCommit<'a> { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.time().partial_cmp(&other.0.time()) - } + fn partial_cmp(&self, other: &Self) -> Option { + self.0.time().partial_cmp(&other.0.time()) + } } impl<'a> Ord for TimeOrderedCommit<'a> { - fn cmp(&self, other: &Self) -> Ordering { - self.0.time().cmp(&other.0.time()) - } + fn cmp(&self, other: &Self) -> Ordering { + self.0.time().cmp(&other.0.time()) + } } /// pub type LogWalkerFilter = Arc< - Box Result + Send + Sync>, + Box Result + Send + Sync>, >; /// pub struct LogWalker<'a> { - commits: BinaryHeap>, - visited: HashSet, - limit: usize, - repo: &'a Repository, - filter: Option, + commits: BinaryHeap>, + visited: HashSet, + limit: usize, + repo: &'a Repository, + filter: Option, } impl<'a> LogWalker<'a> { - /// - pub fn new(repo: &'a Repository, limit: usize) -> Result { - let c = repo.head()?.peel_to_commit()?; + /// + pub fn new(repo: &'a Repository, limit: usize) -> Result { + let c = repo.head()?.peel_to_commit()?; - let mut commits = BinaryHeap::with_capacity(10); - commits.push(TimeOrderedCommit(c)); + let mut commits = BinaryHeap::with_capacity(10); + commits.push(TimeOrderedCommit(c)); - Ok(Self { - commits, - limit, - visited: HashSet::with_capacity(1000), - repo, - filter: None, - }) - } + Ok(Self { + commits, + limit, + visited: HashSet::with_capacity(1000), + repo, + filter: None, + }) + } - /// - pub fn filter(self, filter: Option) -> Self { - Self { filter, ..self } - } + /// + pub fn filter(self, filter: Option) -> Self { + Self { filter, ..self } + } - /// - pub fn read(&mut self, out: &mut Vec) -> Result { - let mut count = 0_usize; + /// + pub fn read(&mut self, out: &mut Vec) -> Result { + let mut count = 0_usize; - while let Some(c) = self.commits.pop() { - for p in c.0.parents() { - self.visit(p); - } + while let Some(c) = self.commits.pop() { + for p in c.0.parents() { + self.visit(p); + } - let id: CommitId = c.0.id().into(); - let commit_should_be_included = - if let Some(ref filter) = self.filter { - filter(self.repo, &id)? - } else { - true - }; + let id: CommitId = c.0.id().into(); + let commit_should_be_included = + if let Some(ref filter) = self.filter { + filter(self.repo, &id)? + } else { + true + }; - if commit_should_be_included { - out.push(id); - } + if commit_should_be_included { + out.push(id); + } - count += 1; - if count == self.limit { - break; - } - } + count += 1; + if count == self.limit { + break; + } + } - Ok(count) - } + Ok(count) + } - // - fn visit(&mut self, c: Commit<'a>) { - if !self.visited.contains(&c.id()) { - self.visited.insert(c.id()); - self.commits.push(TimeOrderedCommit(c)); - } - } + // + fn visit(&mut self, c: Commit<'a>) { + if !self.visited.contains(&c.id()) { + self.visited.insert(c.id()); + self.commits.push(TimeOrderedCommit(c)); + } + } } #[cfg(test)] mod tests { - use super::*; - use crate::error::Result; - use crate::sync::{ - commit, commit_files::get_commit_diff, get_commits_info, - stage_add_file, tests::repo_init_empty, - }; - use pretty_assertions::assert_eq; - use std::{fs::File, io::Write, path::Path}; + use super::*; + use crate::error::Result; + use crate::sync::{ + commit, commit_files::get_commit_diff, get_commits_info, + stage_add_file, tests::repo_init_empty, + }; + use pretty_assertions::assert_eq; + use std::{fs::File, io::Write, path::Path}; - #[test] - fn test_limit() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_limit() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - commit(repo_path, "commit1").unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - let oid2 = commit(repo_path, "commit2").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + commit(repo_path, "commit1").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + let oid2 = commit(repo_path, "commit2").unwrap(); - let mut items = Vec::new(); - let mut walk = LogWalker::new(&repo, 1)?; - walk.read(&mut items).unwrap(); + let mut items = Vec::new(); + let mut walk = LogWalker::new(&repo, 1)?; + walk.read(&mut items).unwrap(); - assert_eq!(items.len(), 1); - assert_eq!(items[0], oid2.into()); + assert_eq!(items.len(), 1); + assert_eq!(items[0], oid2.into()); - Ok(()) - } + Ok(()) + } - #[test] - fn test_logwalker() -> Result<()> { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_logwalker() -> Result<()> { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - commit(repo_path, "commit1").unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); - let oid2 = commit(repo_path, "commit2").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + commit(repo_path, "commit1").unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); + let oid2 = commit(repo_path, "commit2").unwrap(); - let mut items = Vec::new(); - let mut walk = LogWalker::new(&repo, 100)?; - walk.read(&mut items).unwrap(); + let mut items = Vec::new(); + let mut walk = LogWalker::new(&repo, 100)?; + walk.read(&mut items).unwrap(); - let info = get_commits_info(repo_path, &items, 50).unwrap(); - dbg!(&info); + let info = get_commits_info(repo_path, &items, 50).unwrap(); + dbg!(&info); - assert_eq!(items.len(), 2); - assert_eq!(items[0], oid2.into()); + assert_eq!(items.len(), 2); + assert_eq!(items[0], oid2.into()); - let mut items = Vec::new(); - walk.read(&mut items).unwrap(); + let mut items = Vec::new(); + walk.read(&mut items).unwrap(); - assert_eq!(items.len(), 0); + assert_eq!(items.len(), 0); - Ok(()) - } + Ok(()) + } - #[test] - fn test_logwalker_with_filter() -> Result<()> { - let file_path = Path::new("foo"); - let second_file_path = Path::new("baz"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_logwalker_with_filter() -> Result<()> { + let file_path = Path::new("foo"); + let second_file_path = Path::new("baz"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path))?.write_all(b"a")?; - stage_add_file(repo_path, file_path).unwrap(); + File::create(&root.join(file_path))?.write_all(b"a")?; + stage_add_file(repo_path, file_path).unwrap(); - let _first_commit_id = commit(repo_path, "commit1").unwrap(); + let _first_commit_id = commit(repo_path, "commit1").unwrap(); - File::create(&root.join(second_file_path))? - .write_all(b"a")?; - stage_add_file(repo_path, second_file_path).unwrap(); + File::create(&root.join(second_file_path))? + .write_all(b"a")?; + stage_add_file(repo_path, second_file_path).unwrap(); - let second_commit_id = commit(repo_path, "commit2").unwrap(); + let second_commit_id = commit(repo_path, "commit2").unwrap(); - File::create(&root.join(file_path))?.write_all(b"b")?; - stage_add_file(repo_path, file_path).unwrap(); + File::create(&root.join(file_path))?.write_all(b"b")?; + stage_add_file(repo_path, file_path).unwrap(); - let _third_commit_id = commit(repo_path, "commit3").unwrap(); + let _third_commit_id = commit(repo_path, "commit3").unwrap(); - let diff_contains_baz = |repo: &Repository, - commit_id: &CommitId| - -> Result { - let diff = get_commit_diff( - &repo, - *commit_id, - Some("baz".into()), - )?; + let diff_contains_baz = |repo: &Repository, + commit_id: &CommitId| + -> Result { + let diff = get_commit_diff( + &repo, + *commit_id, + Some("baz".into()), + )?; - let contains_file = diff.deltas().len() > 0; + let contains_file = diff.deltas().len() > 0; - Ok(contains_file) - }; + Ok(contains_file) + }; - let mut items = Vec::new(); - let mut walker = LogWalker::new(&repo, 100)? - .filter(Some(Arc::new(Box::new(diff_contains_baz)))); - walker.read(&mut items).unwrap(); + let mut items = Vec::new(); + let mut walker = LogWalker::new(&repo, 100)? + .filter(Some(Arc::new(Box::new(diff_contains_baz)))); + walker.read(&mut items).unwrap(); - assert_eq!(items.len(), 1); - assert_eq!(items[0], second_commit_id.into()); + assert_eq!(items.len(), 1); + assert_eq!(items[0], second_commit_id.into()); - let mut items = Vec::new(); - walker.read(&mut items).unwrap(); + let mut items = Vec::new(); + walker.read(&mut items).unwrap(); - assert_eq!(items.len(), 0); + assert_eq!(items.len(), 0); - let diff_contains_bar = |repo: &Repository, - commit_id: &CommitId| - -> Result { - let diff = get_commit_diff( - &repo, - *commit_id, - Some("bar".into()), - )?; + let diff_contains_bar = |repo: &Repository, + commit_id: &CommitId| + -> Result { + let diff = get_commit_diff( + &repo, + *commit_id, + Some("bar".into()), + )?; - let contains_file = diff.deltas().len() > 0; + let contains_file = diff.deltas().len() > 0; - Ok(contains_file) - }; + Ok(contains_file) + }; - let mut items = Vec::new(); - let mut walker = LogWalker::new(&repo, 100)? - .filter(Some(Arc::new(Box::new(diff_contains_bar)))); - walker.read(&mut items).unwrap(); + let mut items = Vec::new(); + let mut walker = LogWalker::new(&repo, 100)? + .filter(Some(Arc::new(Box::new(diff_contains_bar)))); + walker.read(&mut items).unwrap(); - assert_eq!(items.len(), 0); + assert_eq!(items.len(), 0); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/sync/merge.rs b/asyncgit/src/sync/merge.rs index a58f7a25..f91b33a4 100644 --- a/asyncgit/src/sync/merge.rs +++ b/asyncgit/src/sync/merge.rs @@ -1,26 +1,26 @@ use crate::{ - error::{Error, Result}, - sync::{ - branch::merge_commit::commit_merge_with_head, reset_stage, - reset_workdir, utils, CommitId, - }, + error::{Error, Result}, + sync::{ + branch::merge_commit::commit_merge_with_head, reset_stage, + reset_workdir, utils, CommitId, + }, }; use git2::{BranchType, Commit, MergeOptions, Repository}; use scopetime::scope_time; /// pub fn mergehead_ids(repo_path: &str) -> Result> { - scope_time!("mergehead_ids"); + scope_time!("mergehead_ids"); - let mut repo = utils::repo(repo_path)?; + let mut repo = utils::repo(repo_path)?; - let mut ids: Vec = Vec::new(); - repo.mergehead_foreach(|id| { - ids.push(CommitId::from(*id)); - true - })?; + let mut ids: Vec = Vec::new(); + repo.mergehead_foreach(|id| { + ids.push(CommitId::from(*id)); + true + })?; - Ok(ids) + Ok(ids) } /// does these steps: @@ -28,114 +28,114 @@ pub fn mergehead_ids(repo_path: &str) -> Result> { /// * revert all changes in workdir /// * cleanup repo merge state pub fn abort_merge(repo_path: &str) -> Result<()> { - scope_time!("cleanup_state"); + scope_time!("cleanup_state"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - reset_stage(repo_path, "*")?; - reset_workdir(repo_path, "*")?; + reset_stage(repo_path, "*")?; + reset_workdir(repo_path, "*")?; - repo.cleanup_state()?; + repo.cleanup_state()?; - Ok(()) + Ok(()) } /// pub fn merge_branch(repo_path: &str, branch: &str) -> Result<()> { - scope_time!("merge_branch"); + scope_time!("merge_branch"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - merge_branch_repo(&repo, branch)?; + merge_branch_repo(&repo, branch)?; - Ok(()) + Ok(()) } /// pub fn merge_branch_repo( - repo: &Repository, - branch: &str, + repo: &Repository, + branch: &str, ) -> Result<()> { - let branch = repo.find_branch(branch, BranchType::Local)?; + let branch = repo.find_branch(branch, BranchType::Local)?; - let annotated = - repo.reference_to_annotated_commit(&branch.into_reference())?; + let annotated = + repo.reference_to_annotated_commit(&branch.into_reference())?; - let (analysis, _) = repo.merge_analysis(&[&annotated])?; + let (analysis, _) = repo.merge_analysis(&[&annotated])?; - //TODO: support merge on unborn - if analysis.is_unborn() { - return Err(Error::Generic("head is unborn".into())); - } + //TODO: support merge on unborn + if analysis.is_unborn() { + return Err(Error::Generic("head is unborn".into())); + } - let mut opt = MergeOptions::default(); + let mut opt = MergeOptions::default(); - repo.merge(&[&annotated], Some(&mut opt), None)?; + repo.merge(&[&annotated], Some(&mut opt), None)?; - Ok(()) + Ok(()) } /// pub fn merge_msg(repo_path: &str) -> Result { - scope_time!("merge_msg"); + scope_time!("merge_msg"); - let repo = utils::repo(repo_path)?; - let content = repo.message()?; + let repo = utils::repo(repo_path)?; + let content = repo.message()?; - Ok(content) + Ok(content) } /// pub fn merge_commit( - repo_path: &str, - msg: &str, - ids: &[CommitId], + repo_path: &str, + msg: &str, + ids: &[CommitId], ) -> Result { - scope_time!("merge_commit"); + scope_time!("merge_commit"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let mut commits: Vec = Vec::new(); + let mut commits: Vec = Vec::new(); - for id in ids { - commits.push(repo.find_commit((*id).into())?); - } + for id in ids { + commits.push(repo.find_commit((*id).into())?); + } - let id = commit_merge_with_head(&repo, &commits, msg)?; + let id = commit_merge_with_head(&repo, &commits, msg)?; - Ok(id) + Ok(id) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::{ - create_branch, - tests::{repo_init, write_commit_file}, - }; - use pretty_assertions::assert_eq; + use super::*; + use crate::sync::{ + create_branch, + tests::{repo_init, write_commit_file}, + }; + use pretty_assertions::assert_eq; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let c1 = - write_commit_file(&repo, "test.txt", "test", "commit1"); + let c1 = + write_commit_file(&repo, "test.txt", "test", "commit1"); - create_branch(repo_path, "foo").unwrap(); + create_branch(repo_path, "foo").unwrap(); - write_commit_file(&repo, "test.txt", "test2", "commit2"); + write_commit_file(&repo, "test.txt", "test2", "commit2"); - merge_branch(repo_path, "master").unwrap(); + merge_branch(repo_path, "master").unwrap(); - let msg = merge_msg(repo_path).unwrap(); + let msg = merge_msg(repo_path).unwrap(); - assert_eq!(&msg[0..12], "Merge branch"); + assert_eq!(&msg[0..12], "Merge branch"); - let mergeheads = mergehead_ids(repo_path).unwrap(); + let mergeheads = mergehead_ids(repo_path).unwrap(); - assert_eq!(mergeheads[0], c1); - } + assert_eq!(mergeheads[0], c1); + } } diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 06caeecb..6d8f4b20 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -30,288 +30,288 @@ pub mod utils; pub use blame::{blame_file, BlameHunk, FileBlame}; pub use branch::{ - branch_compare_upstream, checkout_branch, config_is_pull_rebase, - create_branch, delete_branch, get_branch_remote, - get_branches_info, merge_commit::merge_upstream_commit, - merge_ff::branch_merge_upstream_fastforward, - merge_rebase::merge_upstream_rebase, rename::rename_branch, - validate_branch_name, BranchCompare, BranchInfo, + branch_compare_upstream, checkout_branch, config_is_pull_rebase, + create_branch, delete_branch, get_branch_remote, + get_branches_info, merge_commit::merge_upstream_commit, + merge_ff::branch_merge_upstream_fastforward, + merge_rebase::merge_upstream_rebase, rename::rename_branch, + validate_branch_name, BranchCompare, BranchInfo, }; pub use commit::{amend, commit, tag}; pub use commit_details::{ - get_commit_details, CommitDetails, CommitMessage, CommitSignature, + get_commit_details, CommitDetails, CommitMessage, CommitSignature, }; pub use commit_files::get_commit_files; pub use commits_info::{ - get_commit_info, get_commits_info, CommitId, CommitInfo, + get_commit_info, get_commits_info, CommitId, CommitInfo, }; pub use config::{ - get_config_string, untracked_files_config, - ShowUntrackedFilesConfig, + get_config_string, untracked_files_config, + ShowUntrackedFilesConfig, }; pub use diff::get_diff_commit; pub use hooks::{ - hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult, + hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult, }; pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; pub use logwalker::{LogWalker, LogWalkerFilter}; pub use merge::{ - abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids, + abort_merge, merge_branch, merge_commit, merge_msg, mergehead_ids, }; pub use remotes::{ - get_default_remote, get_remotes, push::AsyncProgress, - tags::PushTagsProgress, + get_default_remote, get_remotes, push::AsyncProgress, + tags::PushTagsProgress, }; pub use reset::{reset_stage, reset_workdir}; pub use staging::{discard_lines, stage_lines}; pub use stash::{ - get_stashes, stash_apply, stash_drop, stash_pop, stash_save, + get_stashes, stash_apply, stash_drop, stash_pop, stash_save, }; pub use state::{repo_state, RepoState}; pub use tags::{ - delete_tag, get_tags, get_tags_with_metadata, CommitTags, - TagWithMetadata, Tags, + delete_tag, get_tags, get_tags_with_metadata, CommitTags, + TagWithMetadata, Tags, }; pub use tree::{tree_file_content, tree_files, TreeFile}; pub use utils::{ - get_head, get_head_tuple, is_bare_repo, is_repo, repo_dir, - stage_add_all, stage_add_file, stage_addremoved, Head, + get_head, get_head_tuple, is_bare_repo, is_repo, repo_dir, + stage_add_all, stage_add_file, stage_addremoved, Head, }; #[cfg(test)] mod tests { - use super::{ - commit, stage_add_file, - status::{get_status, StatusType}, - utils::{get_head_repo, repo, repo_write_file}, - CommitId, LogWalker, - }; - use crate::error::Result; - use git2::Repository; - use std::{path::Path, process::Command}; - use tempfile::TempDir; + use super::{ + commit, stage_add_file, + status::{get_status, StatusType}, + utils::{get_head_repo, repo, repo_write_file}, + CommitId, LogWalker, + }; + use crate::error::Result; + use git2::Repository; + use std::{path::Path, process::Command}; + use tempfile::TempDir; - /// Calling `set_search_path` with an empty directory makes sure that there - /// is no git config interfering with our tests (for example user-local - /// `.gitconfig`). - #[allow(unsafe_code)] - fn sandbox_config_files() { - use git2::{opts::set_search_path, ConfigLevel}; - use std::sync::Once; + /// Calling `set_search_path` with an empty directory makes sure that there + /// is no git config interfering with our tests (for example user-local + /// `.gitconfig`). + #[allow(unsafe_code)] + fn sandbox_config_files() { + use git2::{opts::set_search_path, ConfigLevel}; + use std::sync::Once; - static INIT: Once = Once::new(); + static INIT: Once = Once::new(); - // Adapted from https://github.com/rust-lang/cargo/pull/9035 - INIT.call_once(|| unsafe { - let temp_dir = TempDir::new().unwrap(); - let path = temp_dir.path(); + // Adapted from https://github.com/rust-lang/cargo/pull/9035 + INIT.call_once(|| unsafe { + let temp_dir = TempDir::new().unwrap(); + let path = temp_dir.path(); - set_search_path(ConfigLevel::System, &path).unwrap(); - set_search_path(ConfigLevel::Global, &path).unwrap(); - set_search_path(ConfigLevel::XDG, &path).unwrap(); - set_search_path(ConfigLevel::ProgramData, &path).unwrap(); - }); - } + set_search_path(ConfigLevel::System, &path).unwrap(); + set_search_path(ConfigLevel::Global, &path).unwrap(); + set_search_path(ConfigLevel::XDG, &path).unwrap(); + set_search_path(ConfigLevel::ProgramData, &path).unwrap(); + }); + } - /// write, stage and commit a file - pub fn write_commit_file( - repo: &Repository, - file: &str, - content: &str, - commit_name: &str, - ) -> CommitId { - repo_write_file(repo, file, content).unwrap(); + /// write, stage and commit a file + pub fn write_commit_file( + repo: &Repository, + file: &str, + content: &str, + commit_name: &str, + ) -> CommitId { + repo_write_file(repo, file, content).unwrap(); - stage_add_file( - repo.workdir().unwrap().to_str().unwrap(), - Path::new(file), - ) - .unwrap(); + stage_add_file( + repo.workdir().unwrap().to_str().unwrap(), + Path::new(file), + ) + .unwrap(); - commit(repo.workdir().unwrap().to_str().unwrap(), commit_name) - .unwrap() - } + commit(repo.workdir().unwrap().to_str().unwrap(), commit_name) + .unwrap() + } - /// write, stage and commit a file giving the commit a specific timestamp - pub fn write_commit_file_at( - repo: &Repository, - file: &str, - content: &str, - commit_name: &str, - time: git2::Time, - ) -> CommitId { - repo_write_file(repo, file, content).unwrap(); + /// write, stage and commit a file giving the commit a specific timestamp + pub fn write_commit_file_at( + repo: &Repository, + file: &str, + content: &str, + commit_name: &str, + time: git2::Time, + ) -> CommitId { + repo_write_file(repo, file, content).unwrap(); - let path = repo.workdir().unwrap().to_str().unwrap(); + let path = repo.workdir().unwrap().to_str().unwrap(); - stage_add_file(path, Path::new(file)).unwrap(); + stage_add_file(path, Path::new(file)).unwrap(); - commit_at(path, commit_name, time) - } + commit_at(path, commit_name, time) + } - fn commit_at( - repo_path: &str, - msg: &str, - time: git2::Time, - ) -> CommitId { - let repo = repo(repo_path).unwrap(); + fn commit_at( + repo_path: &str, + msg: &str, + time: git2::Time, + ) -> CommitId { + let repo = repo(repo_path).unwrap(); - let signature = - git2::Signature::new("name", "email", &time).unwrap(); - let mut index = repo.index().unwrap(); - let tree_id = index.write_tree().unwrap(); - let tree = repo.find_tree(tree_id).unwrap(); + let signature = + git2::Signature::new("name", "email", &time).unwrap(); + let mut index = repo.index().unwrap(); + let tree_id = index.write_tree().unwrap(); + let tree = repo.find_tree(tree_id).unwrap(); - let parents = if let Ok(id) = get_head_repo(&repo) { - vec![repo.find_commit(id.into()).unwrap()] - } else { - Vec::new() - }; + let parents = if let Ok(id) = get_head_repo(&repo) { + vec![repo.find_commit(id.into()).unwrap()] + } else { + Vec::new() + }; - let parents = parents.iter().collect::>(); + let parents = parents.iter().collect::>(); - let commit = repo - .commit( - Some("HEAD"), - &signature, - &signature, - msg, - &tree, - parents.as_slice(), - ) - .unwrap() - .into(); + let commit = repo + .commit( + Some("HEAD"), + &signature, + &signature, + msg, + &tree, + parents.as_slice(), + ) + .unwrap() + .into(); - commit - } + commit + } - /// - pub fn repo_init_empty() -> Result<(TempDir, Repository)> { - sandbox_config_files(); + /// + pub fn repo_init_empty() -> Result<(TempDir, Repository)> { + sandbox_config_files(); - let td = TempDir::new()?; - let repo = Repository::init(td.path())?; - { - let mut config = repo.config()?; - config.set_str("user.name", "name")?; - config.set_str("user.email", "email")?; - } - Ok((td, repo)) - } + let td = TempDir::new()?; + let repo = Repository::init(td.path())?; + { + let mut config = repo.config()?; + config.set_str("user.name", "name")?; + config.set_str("user.email", "email")?; + } + Ok((td, repo)) + } - /// - pub fn repo_init() -> Result<(TempDir, Repository)> { - sandbox_config_files(); + /// + pub fn repo_init() -> Result<(TempDir, Repository)> { + sandbox_config_files(); - let td = TempDir::new()?; - let repo = Repository::init(td.path())?; - { - let mut config = repo.config()?; - config.set_str("user.name", "name")?; - config.set_str("user.email", "email")?; + let td = TempDir::new()?; + let repo = Repository::init(td.path())?; + { + let mut config = repo.config()?; + config.set_str("user.name", "name")?; + config.set_str("user.email", "email")?; - let mut index = repo.index()?; - let id = index.write_tree()?; + let mut index = repo.index()?; + let id = index.write_tree()?; - let tree = repo.find_tree(id)?; - let sig = repo.signature()?; - repo.commit( - Some("HEAD"), - &sig, - &sig, - "initial", - &tree, - &[], - )?; - } - Ok((td, repo)) - } + let tree = repo.find_tree(id)?; + let sig = repo.signature()?; + repo.commit( + Some("HEAD"), + &sig, + &sig, + "initial", + &tree, + &[], + )?; + } + Ok((td, repo)) + } - /// - pub fn repo_clone(p: &str) -> Result<(TempDir, Repository)> { - sandbox_config_files(); + /// + pub fn repo_clone(p: &str) -> Result<(TempDir, Repository)> { + sandbox_config_files(); - let td = TempDir::new()?; + let td = TempDir::new()?; - let td_path = td.path().as_os_str().to_str().unwrap(); + let td_path = td.path().as_os_str().to_str().unwrap(); - let repo = Repository::clone(p, td_path).unwrap(); + let repo = Repository::clone(p, td_path).unwrap(); - let mut config = repo.config()?; - config.set_str("user.name", "name")?; - config.set_str("user.email", "email")?; + let mut config = repo.config()?; + config.set_str("user.name", "name")?; + config.set_str("user.email", "email")?; - Ok((td, repo)) - } + Ok((td, repo)) + } - /// Same as repo_init, but the repo is a bare repo (--bare) - pub fn repo_init_bare() -> Result<(TempDir, Repository)> { - let tmp_repo_dir = TempDir::new()?; - let bare_repo = Repository::init_bare(tmp_repo_dir.path())?; - Ok((tmp_repo_dir, bare_repo)) - } + /// Same as repo_init, but the repo is a bare repo (--bare) + pub fn repo_init_bare() -> Result<(TempDir, Repository)> { + let tmp_repo_dir = TempDir::new()?; + let bare_repo = Repository::init_bare(tmp_repo_dir.path())?; + Ok((tmp_repo_dir, bare_repo)) + } - /// helper returning amount of files with changes in the (wd,stage) - pub fn get_statuses(repo_path: &str) -> (usize, usize) { - ( - get_status(repo_path, StatusType::WorkingDir) - .unwrap() - .len(), - get_status(repo_path, StatusType::Stage).unwrap().len(), - ) - } + /// helper returning amount of files with changes in the (wd,stage) + pub fn get_statuses(repo_path: &str) -> (usize, usize) { + ( + get_status(repo_path, StatusType::WorkingDir) + .unwrap() + .len(), + get_status(repo_path, StatusType::Stage).unwrap().len(), + ) + } - /// - pub fn debug_cmd_print(path: &str, cmd: &str) { - let cmd = debug_cmd(path, cmd); - eprintln!("\n----\n{}", cmd); - } + /// + pub fn debug_cmd_print(path: &str, cmd: &str) { + let cmd = debug_cmd(path, cmd); + eprintln!("\n----\n{}", cmd); + } - /// helper to fetch commmit details using log walker - pub fn get_commit_ids( - r: &Repository, - max_count: usize, - ) -> Vec { - let mut commit_ids = Vec::::new(); - LogWalker::new(r, max_count) - .unwrap() - .read(&mut commit_ids) - .unwrap(); + /// helper to fetch commmit details using log walker + pub fn get_commit_ids( + r: &Repository, + max_count: usize, + ) -> Vec { + let mut commit_ids = Vec::::new(); + LogWalker::new(r, max_count) + .unwrap() + .read(&mut commit_ids) + .unwrap(); - commit_ids - } + commit_ids + } - fn debug_cmd(path: &str, cmd: &str) -> String { - let output = if cfg!(target_os = "windows") { - Command::new("cmd") - .args(&["/C", cmd]) - .current_dir(path) - .output() - .unwrap() - } else { - Command::new("sh") - .arg("-c") - .arg(cmd) - .current_dir(path) - .output() - .unwrap() - }; + fn debug_cmd(path: &str, cmd: &str) -> String { + let output = if cfg!(target_os = "windows") { + Command::new("cmd") + .args(&["/C", cmd]) + .current_dir(path) + .output() + .unwrap() + } else { + Command::new("sh") + .arg("-c") + .arg(cmd) + .current_dir(path) + .output() + .unwrap() + }; - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); - format!( - "{}{}", - if stdout.is_empty() { - String::new() - } else { - format!("out:\n{}", stdout) - }, - if stderr.is_empty() { - String::new() - } else { - format!("err:\n{}", stderr) - } - ) - } + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + format!( + "{}{}", + if stdout.is_empty() { + String::new() + } else { + format!("out:\n{}", stdout) + }, + if stderr.is_empty() { + String::new() + } else { + format!("err:\n{}", stderr) + } + ) + } } diff --git a/asyncgit/src/sync/patches.rs b/asyncgit/src/sync/patches.rs index 8d3db37b..79db2967 100644 --- a/asyncgit/src/sync/patches.rs +++ b/asyncgit/src/sync/patches.rs @@ -4,70 +4,70 @@ use git2::{Diff, DiffLine, Patch, Repository}; #[allow(clippy::redundant_pub_crate)] pub(crate) struct HunkLines<'a> { - pub hunk: HunkHeader, - pub lines: Vec>, + pub hunk: HunkHeader, + pub lines: Vec>, } #[allow(clippy::redundant_pub_crate)] pub(crate) fn get_file_diff_patch_and_hunklines<'a>( - repo: &'a Repository, - file: &str, - is_staged: bool, - reverse: bool, + repo: &'a Repository, + file: &str, + is_staged: bool, + reverse: bool, ) -> Result<(Patch<'a>, Vec>)> { - let diff = get_diff_raw(repo, file, is_staged, reverse, Some(1))?; - let patches = get_patches(&diff)?; - if patches.len() > 1 { - return Err(Error::Generic(String::from("patch error"))); - } + let diff = get_diff_raw(repo, file, is_staged, reverse, Some(1))?; + let patches = get_patches(&diff)?; + if patches.len() > 1 { + return Err(Error::Generic(String::from("patch error"))); + } - let patch = patches.into_iter().next().ok_or_else(|| { - Error::Generic(String::from("no patch found")) - })?; + let patch = patches.into_iter().next().ok_or_else(|| { + Error::Generic(String::from("no patch found")) + })?; - let lines = patch_get_hunklines(&patch)?; + let lines = patch_get_hunklines(&patch)?; - Ok((patch, lines)) + Ok((patch, lines)) } // fn patch_get_hunklines<'a>( - patch: &Patch<'a>, + patch: &Patch<'a>, ) -> Result>> { - let count_hunks = patch.num_hunks(); - let mut res = Vec::with_capacity(count_hunks); - for hunk_idx in 0..count_hunks { - let (hunk, _) = patch.hunk(hunk_idx)?; + let count_hunks = patch.num_hunks(); + let mut res = Vec::with_capacity(count_hunks); + for hunk_idx in 0..count_hunks { + let (hunk, _) = patch.hunk(hunk_idx)?; - let count_lines = patch.num_lines_in_hunk(hunk_idx)?; + let count_lines = patch.num_lines_in_hunk(hunk_idx)?; - let mut hunk = HunkLines { - hunk: HunkHeader::from(hunk), - lines: Vec::with_capacity(count_lines), - }; + let mut hunk = HunkLines { + hunk: HunkHeader::from(hunk), + lines: Vec::with_capacity(count_lines), + }; - for line_idx in 0..count_lines { - let line = patch.line_in_hunk(hunk_idx, line_idx)?; - hunk.lines.push(line); - } + for line_idx in 0..count_lines { + let line = patch.line_in_hunk(hunk_idx, line_idx)?; + hunk.lines.push(line); + } - res.push(hunk); - } + res.push(hunk); + } - Ok(res) + Ok(res) } // fn get_patches<'a>(diff: &Diff<'a>) -> Result>> { - let count = diff.deltas().len(); + let count = diff.deltas().len(); - let mut res = Vec::with_capacity(count); - for idx in 0..count { - let p = Patch::from_diff(diff, idx)?; - if let Some(p) = p { - res.push(p); - } - } + let mut res = Vec::with_capacity(count); + for idx in 0..count { + let p = Patch::from_diff(diff, idx)?; + if let Some(p) = p { + res.push(p); + } + } - Ok(res) + Ok(res) } diff --git a/asyncgit/src/sync/remotes/callbacks.rs b/asyncgit/src/sync/remotes/callbacks.rs index 4f8b82c5..6cf39923 100644 --- a/asyncgit/src/sync/remotes/callbacks.rs +++ b/asyncgit/src/sync/remotes/callbacks.rs @@ -5,219 +5,219 @@ use crate::{error::Result, sync::cred::BasicAuthCredential}; use crossbeam_channel::Sender; use git2::{Cred, Error as GitError, RemoteCallbacks}; use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, + atomic::{AtomicBool, Ordering}, + Arc, Mutex, }; /// #[derive(Default, Clone)] pub struct CallbackStats { - pub push_rejected_msg: Option<(String, String)>, + pub push_rejected_msg: Option<(String, String)>, } /// #[derive(Clone)] pub struct Callbacks { - sender: Option>, - basic_credential: Option, - stats: Arc>, - first_call_to_credentials: Arc, + sender: Option>, + basic_credential: Option, + stats: Arc>, + first_call_to_credentials: Arc, } impl Callbacks { - /// - pub fn new( - sender: Option>, - basic_credential: Option, - ) -> Self { - let stats = Arc::new(Mutex::new(CallbackStats::default())); + /// + pub fn new( + sender: Option>, + basic_credential: Option, + ) -> Self { + let stats = Arc::new(Mutex::new(CallbackStats::default())); - Self { - sender, - basic_credential, - stats, - first_call_to_credentials: Arc::new(AtomicBool::new( - true, - )), - } - } + Self { + sender, + basic_credential, + stats, + first_call_to_credentials: Arc::new(AtomicBool::new( + true, + )), + } + } - /// - pub fn get_stats(&self) -> Result { - let stats = self.stats.lock()?; - Ok(stats.clone()) - } + /// + pub fn get_stats(&self) -> Result { + let stats = self.stats.lock()?; + Ok(stats.clone()) + } - /// - pub fn callbacks<'a>(&self) -> RemoteCallbacks<'a> { - let mut callbacks = RemoteCallbacks::new(); + /// + pub fn callbacks<'a>(&self) -> RemoteCallbacks<'a> { + let mut callbacks = RemoteCallbacks::new(); - let this = self.clone(); - callbacks.push_transfer_progress( - move |current, total, bytes| { - this.push_transfer_progress(current, total, bytes); - }, - ); + let this = self.clone(); + callbacks.push_transfer_progress( + move |current, total, bytes| { + this.push_transfer_progress(current, total, bytes); + }, + ); - let this = self.clone(); - callbacks.update_tips(move |name, a, b| { - this.update_tips(name, a, b); - true - }); + let this = self.clone(); + callbacks.update_tips(move |name, a, b| { + this.update_tips(name, a, b); + true + }); - let this = self.clone(); - callbacks.transfer_progress(move |p| { - this.transfer_progress(&p); - true - }); + let this = self.clone(); + callbacks.transfer_progress(move |p| { + this.transfer_progress(&p); + true + }); - let this = self.clone(); - callbacks.pack_progress(move |stage, current, total| { - this.pack_progress(stage, total, current); - }); + let this = self.clone(); + callbacks.pack_progress(move |stage, current, total| { + this.pack_progress(stage, total, current); + }); - let this = self.clone(); - callbacks.push_update_reference(move |reference, msg| { - this.push_update_reference(reference, msg); - Ok(()) - }); + let this = self.clone(); + callbacks.push_update_reference(move |reference, msg| { + this.push_update_reference(reference, msg); + Ok(()) + }); - let this = self.clone(); - callbacks.credentials( - move |url, username_from_url, allowed_types| { - this.credentials( - url, - username_from_url, - allowed_types, - ) - }, - ); + let this = self.clone(); + callbacks.credentials( + move |url, username_from_url, allowed_types| { + this.credentials( + url, + username_from_url, + allowed_types, + ) + }, + ); - callbacks - } + callbacks + } - fn push_update_reference( - &self, - reference: &str, - msg: Option<&str>, - ) { - log::debug!( - "push_update_reference: '{}' {:?}", - reference, - msg - ); + fn push_update_reference( + &self, + reference: &str, + msg: Option<&str>, + ) { + log::debug!( + "push_update_reference: '{}' {:?}", + reference, + msg + ); - if let Ok(mut stats) = self.stats.lock() { - stats.push_rejected_msg = msg - .map(|msg| (reference.to_string(), msg.to_string())); - } - } + if let Ok(mut stats) = self.stats.lock() { + stats.push_rejected_msg = msg + .map(|msg| (reference.to_string(), msg.to_string())); + } + } - fn pack_progress( - &self, - stage: git2::PackBuilderStage, - total: usize, - current: usize, - ) { - log::debug!("packing: {:?} - {}/{}", stage, current, total); - self.sender.clone().map(|sender| { - sender.send(ProgressNotification::Packing { - stage, - total, - current, - }) - }); - } + fn pack_progress( + &self, + stage: git2::PackBuilderStage, + total: usize, + current: usize, + ) { + log::debug!("packing: {:?} - {}/{}", stage, current, total); + self.sender.clone().map(|sender| { + sender.send(ProgressNotification::Packing { + stage, + total, + current, + }) + }); + } - fn transfer_progress(&self, p: &git2::Progress) { - log::debug!( - "transfer: {}/{}", - p.received_objects(), - p.total_objects() - ); - self.sender.clone().map(|sender| { - sender.send(ProgressNotification::Transfer { - objects: p.received_objects(), - total_objects: p.total_objects(), - }) - }); - } + fn transfer_progress(&self, p: &git2::Progress) { + log::debug!( + "transfer: {}/{}", + p.received_objects(), + p.total_objects() + ); + self.sender.clone().map(|sender| { + sender.send(ProgressNotification::Transfer { + objects: p.received_objects(), + total_objects: p.total_objects(), + }) + }); + } - fn update_tips(&self, name: &str, a: git2::Oid, b: git2::Oid) { - log::debug!("update tips: '{}' [{}] [{}]", name, a, b); - self.sender.clone().map(|sender| { - sender.send(ProgressNotification::UpdateTips { - name: name.to_string(), - a: a.into(), - b: b.into(), - }) - }); - } + fn update_tips(&self, name: &str, a: git2::Oid, b: git2::Oid) { + log::debug!("update tips: '{}' [{}] [{}]", name, a, b); + self.sender.clone().map(|sender| { + sender.send(ProgressNotification::UpdateTips { + name: name.to_string(), + a: a.into(), + b: b.into(), + }) + }); + } - fn push_transfer_progress( - &self, - current: usize, - total: usize, - bytes: usize, - ) { - log::debug!("progress: {}/{} ({} B)", current, total, bytes,); - self.sender.clone().map(|sender| { - sender.send(ProgressNotification::PushTransfer { - current, - total, - bytes, - }) - }); - } + fn push_transfer_progress( + &self, + current: usize, + total: usize, + bytes: usize, + ) { + log::debug!("progress: {}/{} ({} B)", current, total, bytes,); + self.sender.clone().map(|sender| { + sender.send(ProgressNotification::PushTransfer { + current, + total, + bytes, + }) + }); + } - // If credentials are bad, we don't ask the user to re-fill their creds. We push an error and they will be able to restart their action (for example a push) and retype their creds. - // This behavior is explained in a issue on git2-rs project : https://github.com/rust-lang/git2-rs/issues/347 - // An implementation reference is done in cargo : https://github.com/rust-lang/cargo/blob/9fb208dddb12a3081230a5fd8f470e01df8faa25/src/cargo/sources/git/utils.rs#L588 - // There is also a guide about libgit2 authentication : https://libgit2.org/docs/guides/authentication/ - fn credentials( - &self, - url: &str, - username_from_url: Option<&str>, - allowed_types: git2::CredentialType, - ) -> std::result::Result { - log::debug!( - "creds: '{}' {:?} ({:?})", - url, - username_from_url, - allowed_types - ); + // If credentials are bad, we don't ask the user to re-fill their creds. We push an error and they will be able to restart their action (for example a push) and retype their creds. + // This behavior is explained in a issue on git2-rs project : https://github.com/rust-lang/git2-rs/issues/347 + // An implementation reference is done in cargo : https://github.com/rust-lang/cargo/blob/9fb208dddb12a3081230a5fd8f470e01df8faa25/src/cargo/sources/git/utils.rs#L588 + // There is also a guide about libgit2 authentication : https://libgit2.org/docs/guides/authentication/ + fn credentials( + &self, + url: &str, + username_from_url: Option<&str>, + allowed_types: git2::CredentialType, + ) -> std::result::Result { + log::debug!( + "creds: '{}' {:?} ({:?})", + url, + username_from_url, + allowed_types + ); - // This boolean is used to avoid multiple calls to credentials callback. - if self.first_call_to_credentials.load(Ordering::Relaxed) { - self.first_call_to_credentials - .store(false, Ordering::Relaxed); - } else { - return Err(GitError::from_str("Bad credentials.")); - } + // This boolean is used to avoid multiple calls to credentials callback. + if self.first_call_to_credentials.load(Ordering::Relaxed) { + self.first_call_to_credentials + .store(false, Ordering::Relaxed); + } else { + return Err(GitError::from_str("Bad credentials.")); + } - match &self.basic_credential { - _ if allowed_types.is_ssh_key() => { - match username_from_url { - Some(username) => { - Cred::ssh_key_from_agent(username) - } - None => Err(GitError::from_str( - " Couldn't extract username from url.", - )), - } - } - Some(BasicAuthCredential { - username: Some(user), - password: Some(pwd), - }) if allowed_types.is_user_pass_plaintext() => { - Cred::userpass_plaintext(user, pwd) - } - Some(BasicAuthCredential { - username: Some(user), - password: _, - }) if allowed_types.is_username() => Cred::username(user), - _ if allowed_types.is_default() => Cred::default(), - _ => Err(GitError::from_str("Couldn't find credentials")), - } - } + match &self.basic_credential { + _ if allowed_types.is_ssh_key() => { + match username_from_url { + Some(username) => { + Cred::ssh_key_from_agent(username) + } + None => Err(GitError::from_str( + " Couldn't extract username from url.", + )), + } + } + Some(BasicAuthCredential { + username: Some(user), + password: Some(pwd), + }) if allowed_types.is_user_pass_plaintext() => { + Cred::userpass_plaintext(user, pwd) + } + Some(BasicAuthCredential { + username: Some(user), + password: _, + }) if allowed_types.is_username() => Cred::username(user), + _ if allowed_types.is_default() => Cred::default(), + _ => Err(GitError::from_str("Couldn't find credentials")), + } + } } diff --git a/asyncgit/src/sync/remotes/mod.rs b/asyncgit/src/sync/remotes/mod.rs index 1366ad83..133c761c 100644 --- a/asyncgit/src/sync/remotes/mod.rs +++ b/asyncgit/src/sync/remotes/mod.rs @@ -5,11 +5,11 @@ pub(crate) mod push; pub(crate) mod tags; use crate::{ - error::{Error, Result}, - sync::{ - cred::BasicAuthCredential, - remotes::push::ProgressNotification, utils, - }, + error::{Error, Result}, + sync::{ + cred::BasicAuthCredential, + remotes::push::ProgressNotification, utils, + }, }; use crossbeam_channel::Sender; use git2::{BranchType, FetchOptions, Repository}; @@ -24,193 +24,193 @@ pub const DEFAULT_REMOTE_NAME: &str = "origin"; /// pub fn get_remotes(repo_path: &str) -> Result> { - scope_time!("get_remotes"); + scope_time!("get_remotes"); - let repo = utils::repo(repo_path)?; - let remotes = repo.remotes()?; - let remotes: Vec = - remotes.iter().flatten().map(String::from).collect(); + let repo = utils::repo(repo_path)?; + let remotes = repo.remotes()?; + let remotes: Vec = + remotes.iter().flatten().map(String::from).collect(); - Ok(remotes) + Ok(remotes) } /// tries to find origin or the only remote that is defined if any /// in case of multiple remotes and none named *origin* we fail pub fn get_default_remote(repo_path: &str) -> Result { - let repo = utils::repo(repo_path)?; - get_default_remote_in_repo(&repo) + let repo = utils::repo(repo_path)?; + get_default_remote_in_repo(&repo) } /// see `get_default_remote` pub(crate) fn get_default_remote_in_repo( - repo: &Repository, + repo: &Repository, ) -> Result { - scope_time!("get_default_remote_in_repo"); + scope_time!("get_default_remote_in_repo"); - let remotes = repo.remotes()?; + let remotes = repo.remotes()?; - // if `origin` exists return that - let found_origin = remotes.iter().any(|r| { - r.map(|r| r == DEFAULT_REMOTE_NAME).unwrap_or_default() - }); - if found_origin { - return Ok(DEFAULT_REMOTE_NAME.into()); - } + // if `origin` exists return that + let found_origin = remotes.iter().any(|r| { + r.map(|r| r == DEFAULT_REMOTE_NAME).unwrap_or_default() + }); + if found_origin { + return Ok(DEFAULT_REMOTE_NAME.into()); + } - //if only one remote exists pick that - if remotes.len() == 1 { - let first_remote = remotes - .iter() - .next() - .flatten() - .map(String::from) - .ok_or_else(|| { - Error::Generic("no remote found".into()) - })?; + //if only one remote exists pick that + if remotes.len() == 1 { + let first_remote = remotes + .iter() + .next() + .flatten() + .map(String::from) + .ok_or_else(|| { + Error::Generic("no remote found".into()) + })?; - return Ok(first_remote); - } + return Ok(first_remote); + } - //inconclusive - Err(Error::NoDefaultRemoteFound) + //inconclusive + Err(Error::NoDefaultRemoteFound) } /// fetches from upstream/remote for `branch` pub(crate) fn fetch( - repo_path: &str, - branch: &str, - basic_credential: Option, - progress_sender: Option>, + repo_path: &str, + branch: &str, + basic_credential: Option, + progress_sender: Option>, ) -> Result { - scope_time!("fetch_origin"); + scope_time!("fetch_origin"); - let repo = utils::repo(repo_path)?; - let branch_ref = repo - .find_branch(branch, BranchType::Local)? - .into_reference(); - let branch_ref = bytes2string(branch_ref.name_bytes())?; - let remote_name = repo.branch_upstream_remote(&branch_ref)?; - let remote_name = bytes2string(&*remote_name)?; - let mut remote = repo.find_remote(&remote_name)?; + let repo = utils::repo(repo_path)?; + let branch_ref = repo + .find_branch(branch, BranchType::Local)? + .into_reference(); + let branch_ref = bytes2string(branch_ref.name_bytes())?; + let remote_name = repo.branch_upstream_remote(&branch_ref)?; + let remote_name = bytes2string(&*remote_name)?; + let mut remote = repo.find_remote(&remote_name)?; - let mut options = FetchOptions::new(); - let callbacks = Callbacks::new(progress_sender, basic_credential); - options.remote_callbacks(callbacks.callbacks()); + let mut options = FetchOptions::new(); + let callbacks = Callbacks::new(progress_sender, basic_credential); + options.remote_callbacks(callbacks.callbacks()); - remote.fetch(&[branch], Some(&mut options), None)?; + remote.fetch(&[branch], Some(&mut options), None)?; - Ok(remote.stats().received_bytes()) + Ok(remote.stats().received_bytes()) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::tests::{ - debug_cmd_print, repo_clone, repo_init, - }; + use super::*; + use crate::sync::tests::{ + debug_cmd_print, repo_clone, repo_init, + }; - #[test] - fn test_smoke() { - let (remote_dir, _remote) = repo_init().unwrap(); - let remote_path = remote_dir.path().to_str().unwrap(); - let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); - let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (remote_dir, _remote) = repo_init().unwrap(); + let remote_path = remote_dir.path().to_str().unwrap(); + let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); + let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); - let remotes = get_remotes(repo_path).unwrap(); + let remotes = get_remotes(repo_path).unwrap(); - assert_eq!(remotes, vec![String::from("origin")]); + assert_eq!(remotes, vec![String::from("origin")]); - fetch(repo_path, "master", None, None).unwrap(); - } + fetch(repo_path, "master", None, None).unwrap(); + } - #[test] - fn test_default_remote() { - let (remote_dir, _remote) = repo_init().unwrap(); - let remote_path = remote_dir.path().to_str().unwrap(); - let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); - let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); + #[test] + fn test_default_remote() { + let (remote_dir, _remote) = repo_init().unwrap(); + let remote_path = remote_dir.path().to_str().unwrap(); + let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); + let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); - debug_cmd_print( - repo_path, - &format!("git remote add second {}", remote_path)[..], - ); + debug_cmd_print( + repo_path, + &format!("git remote add second {}", remote_path)[..], + ); - let remotes = get_remotes(repo_path).unwrap(); + let remotes = get_remotes(repo_path).unwrap(); - assert_eq!( - remotes, - vec![String::from("origin"), String::from("second")] - ); + assert_eq!( + remotes, + vec![String::from("origin"), String::from("second")] + ); - let first = get_default_remote_in_repo( - &utils::repo(repo_path).unwrap(), - ) - .unwrap(); - assert_eq!(first, String::from("origin")); - } + let first = get_default_remote_in_repo( + &utils::repo(repo_path).unwrap(), + ) + .unwrap(); + assert_eq!(first, String::from("origin")); + } - #[test] - fn test_default_remote_out_of_order() { - let (remote_dir, _remote) = repo_init().unwrap(); - let remote_path = remote_dir.path().to_str().unwrap(); - let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); - let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); + #[test] + fn test_default_remote_out_of_order() { + let (remote_dir, _remote) = repo_init().unwrap(); + let remote_path = remote_dir.path().to_str().unwrap(); + let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); + let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); - debug_cmd_print( - repo_path, - "git remote rename origin alternate", - ); + debug_cmd_print( + repo_path, + "git remote rename origin alternate", + ); - debug_cmd_print( - repo_path, - &format!("git remote add origin {}", remote_path)[..], - ); + debug_cmd_print( + repo_path, + &format!("git remote add origin {}", remote_path)[..], + ); - //NOTE: aparently remotes are not chronolically sorted but alphabetically - let remotes = get_remotes(repo_path).unwrap(); + //NOTE: aparently remotes are not chronolically sorted but alphabetically + let remotes = get_remotes(repo_path).unwrap(); - assert_eq!( - remotes, - vec![String::from("alternate"), String::from("origin")] - ); + assert_eq!( + remotes, + vec![String::from("alternate"), String::from("origin")] + ); - let first = get_default_remote_in_repo( - &utils::repo(repo_path).unwrap(), - ) - .unwrap(); - assert_eq!(first, String::from("origin")); - } + let first = get_default_remote_in_repo( + &utils::repo(repo_path).unwrap(), + ) + .unwrap(); + assert_eq!(first, String::from("origin")); + } - #[test] - fn test_default_remote_inconclusive() { - let (remote_dir, _remote) = repo_init().unwrap(); - let remote_path = remote_dir.path().to_str().unwrap(); - let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); - let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); + #[test] + fn test_default_remote_inconclusive() { + let (remote_dir, _remote) = repo_init().unwrap(); + let remote_path = remote_dir.path().to_str().unwrap(); + let (repo_dir, _repo) = repo_clone(remote_path).unwrap(); + let repo_path = repo_dir.path().as_os_str().to_str().unwrap(); - debug_cmd_print( - repo_path, - "git remote rename origin alternate", - ); + debug_cmd_print( + repo_path, + "git remote rename origin alternate", + ); - debug_cmd_print( - repo_path, - &format!("git remote add someremote {}", remote_path)[..], - ); + debug_cmd_print( + repo_path, + &format!("git remote add someremote {}", remote_path)[..], + ); - let remotes = get_remotes(repo_path).unwrap(); - assert_eq!( - remotes, - vec![ - String::from("alternate"), - String::from("someremote") - ] - ); + let remotes = get_remotes(repo_path).unwrap(); + assert_eq!( + remotes, + vec![ + String::from("alternate"), + String::from("someremote") + ] + ); - let res = get_default_remote_in_repo( - &utils::repo(repo_path).unwrap(), - ); - assert_eq!(res.is_err(), true); - assert!(matches!(res, Err(Error::NoDefaultRemoteFound))); - } + let res = get_default_remote_in_repo( + &utils::repo(repo_path).unwrap(), + ); + assert_eq!(res.is_err(), true); + assert!(matches!(res, Err(Error::NoDefaultRemoteFound))); + } } diff --git a/asyncgit/src/sync/remotes/push.rs b/asyncgit/src/sync/remotes/push.rs index df0d7cfe..6cef918e 100644 --- a/asyncgit/src/sync/remotes/push.rs +++ b/asyncgit/src/sync/remotes/push.rs @@ -1,11 +1,11 @@ use super::utils; use crate::{ - error::{Error, Result}, - progress::ProgressPercent, - sync::{ - branch::branch_set_upstream, cred::BasicAuthCredential, - remotes::Callbacks, CommitId, - }, + error::{Error, Result}, + progress::ProgressPercent, + sync::{ + branch::branch_set_upstream, cred::BasicAuthCredential, + remotes::Callbacks, CommitId, + }, }; use crossbeam_channel::Sender; use git2::{PackBuilderStage, PushOptions}; @@ -13,467 +13,467 @@ use scopetime::scope_time; /// pub trait AsyncProgress: Clone + Send + Sync { - /// - fn is_done(&self) -> bool; - /// - fn progress(&self) -> ProgressPercent; + /// + fn is_done(&self) -> bool; + /// + fn progress(&self) -> ProgressPercent; } /// #[derive(Debug, Clone, PartialEq)] pub enum ProgressNotification { - /// - UpdateTips { - /// - name: String, - /// - a: CommitId, - /// - b: CommitId, - }, - /// - Transfer { - /// - objects: usize, - /// - total_objects: usize, - }, - /// - PushTransfer { - /// - current: usize, - /// - total: usize, - /// - bytes: usize, - }, - /// - Packing { - /// - stage: PackBuilderStage, - /// - total: usize, - /// - current: usize, - }, - /// - Done, + /// + UpdateTips { + /// + name: String, + /// + a: CommitId, + /// + b: CommitId, + }, + /// + Transfer { + /// + objects: usize, + /// + total_objects: usize, + }, + /// + PushTransfer { + /// + current: usize, + /// + total: usize, + /// + bytes: usize, + }, + /// + Packing { + /// + stage: PackBuilderStage, + /// + total: usize, + /// + current: usize, + }, + /// + Done, } impl AsyncProgress for ProgressNotification { - fn is_done(&self) -> bool { - *self == Self::Done - } - fn progress(&self) -> ProgressPercent { - match *self { - Self::Packing { - stage, - current, - total, - } => match stage { - PackBuilderStage::AddingObjects - | PackBuilderStage::Deltafication => { - ProgressPercent::new(current, total) - } - }, - Self::PushTransfer { current, total, .. } => { - ProgressPercent::new(current, total) - } - Self::Transfer { - objects, - total_objects, - .. - } => ProgressPercent::new(objects, total_objects), - _ => ProgressPercent::full(), - } - } + fn is_done(&self) -> bool { + *self == Self::Done + } + fn progress(&self) -> ProgressPercent { + match *self { + Self::Packing { + stage, + current, + total, + } => match stage { + PackBuilderStage::AddingObjects + | PackBuilderStage::Deltafication => { + ProgressPercent::new(current, total) + } + }, + Self::PushTransfer { current, total, .. } => { + ProgressPercent::new(current, total) + } + Self::Transfer { + objects, + total_objects, + .. + } => ProgressPercent::new(objects, total_objects), + _ => ProgressPercent::full(), + } + } } #[allow(clippy::redundant_pub_crate)] pub(crate) fn push( - repo_path: &str, - remote: &str, - branch: &str, - force: bool, - delete: bool, - basic_credential: Option, - progress_sender: Option>, + repo_path: &str, + remote: &str, + branch: &str, + force: bool, + delete: bool, + basic_credential: Option, + progress_sender: Option>, ) -> Result<()> { - scope_time!("push"); + scope_time!("push"); - let repo = utils::repo(repo_path)?; - let mut remote = repo.find_remote(remote)?; + let repo = utils::repo(repo_path)?; + let mut remote = repo.find_remote(remote)?; - let mut options = PushOptions::new(); + let mut options = PushOptions::new(); - let callbacks = Callbacks::new(progress_sender, basic_credential); - options.remote_callbacks(callbacks.callbacks()); - options.packbuilder_parallelism(0); + let callbacks = Callbacks::new(progress_sender, basic_credential); + options.remote_callbacks(callbacks.callbacks()); + options.packbuilder_parallelism(0); - let branch_modifier = match (force, delete) { - (true, true) => "+:", - (false, true) => ":", - (true, false) => "+", - (false, false) => "", - }; - let branch_name = - format!("{}refs/heads/{}", branch_modifier, branch); - remote.push(&[branch_name.as_str()], Some(&mut options))?; + let branch_modifier = match (force, delete) { + (true, true) => "+:", + (false, true) => ":", + (true, false) => "+", + (false, false) => "", + }; + let branch_name = + format!("{}refs/heads/{}", branch_modifier, branch); + remote.push(&[branch_name.as_str()], Some(&mut options))?; - if let Some((reference, msg)) = - callbacks.get_stats()?.push_rejected_msg - { - return Err(Error::Generic(format!( - "push to '{}' rejected: {}", - reference, msg - ))); - } + if let Some((reference, msg)) = + callbacks.get_stats()?.push_rejected_msg + { + return Err(Error::Generic(format!( + "push to '{}' rejected: {}", + reference, msg + ))); + } - if !delete { - branch_set_upstream(&repo, branch)?; - } + if !delete { + branch_set_upstream(&repo, branch)?; + } - Ok(()) + Ok(()) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::{ - self, - tests::{ - get_commit_ids, repo_clone, repo_init, repo_init_bare, - write_commit_file, - }, - }; - use git2::Repository; - use std::{fs::File, io::Write, path::Path}; + use super::*; + use crate::sync::{ + self, + tests::{ + get_commit_ids, repo_clone, repo_init, repo_init_bare, + write_commit_file, + }, + }; + use git2::Repository; + use std::{fs::File, io::Write, path::Path}; - #[test] - fn test_force_push() { - // This test mimics the scenario of 2 people having 2 - // local branches and both modifying the same file then - // both pushing, sequentially - let (tmp_repo_dir, repo) = repo_init().unwrap(); - let (tmp_other_repo_dir, other_repo) = repo_init().unwrap(); - let (tmp_upstream_dir, _) = repo_init_bare().unwrap(); + #[test] + fn test_force_push() { + // This test mimics the scenario of 2 people having 2 + // local branches and both modifying the same file then + // both pushing, sequentially + let (tmp_repo_dir, repo) = repo_init().unwrap(); + let (tmp_other_repo_dir, other_repo) = repo_init().unwrap(); + let (tmp_upstream_dir, _) = repo_init_bare().unwrap(); - repo.remote( - "origin", - tmp_upstream_dir.path().to_str().unwrap(), - ) - .unwrap(); + repo.remote( + "origin", + tmp_upstream_dir.path().to_str().unwrap(), + ) + .unwrap(); - other_repo - .remote( - "origin", - tmp_upstream_dir.path().to_str().unwrap(), - ) - .unwrap(); + other_repo + .remote( + "origin", + tmp_upstream_dir.path().to_str().unwrap(), + ) + .unwrap(); - let tmp_repo_file_path = - tmp_repo_dir.path().join("temp_file.txt"); - let mut tmp_repo_file = - File::create(tmp_repo_file_path).unwrap(); - writeln!(tmp_repo_file, "TempSomething").unwrap(); + let tmp_repo_file_path = + tmp_repo_dir.path().join("temp_file.txt"); + let mut tmp_repo_file = + File::create(tmp_repo_file_path).unwrap(); + writeln!(tmp_repo_file, "TempSomething").unwrap(); - sync::commit( - tmp_repo_dir.path().to_str().unwrap(), - "repo_1_commit", - ) - .unwrap(); + sync::commit( + tmp_repo_dir.path().to_str().unwrap(), + "repo_1_commit", + ) + .unwrap(); - push( - tmp_repo_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + tmp_repo_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - let tmp_other_repo_file_path = - tmp_other_repo_dir.path().join("temp_file.txt"); - let mut tmp_other_repo_file = - File::create(tmp_other_repo_file_path).unwrap(); - writeln!(tmp_other_repo_file, "TempElse").unwrap(); + let tmp_other_repo_file_path = + tmp_other_repo_dir.path().join("temp_file.txt"); + let mut tmp_other_repo_file = + File::create(tmp_other_repo_file_path).unwrap(); + writeln!(tmp_other_repo_file, "TempElse").unwrap(); - sync::commit( - tmp_other_repo_dir.path().to_str().unwrap(), - "repo_2_commit", - ) - .unwrap(); + sync::commit( + tmp_other_repo_dir.path().to_str().unwrap(), + "repo_2_commit", + ) + .unwrap(); - // Attempt a normal push, - // should fail as branches diverged - assert_eq!( - push( - tmp_other_repo_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .is_err(), - true - ); + // Attempt a normal push, + // should fail as branches diverged + assert_eq!( + push( + tmp_other_repo_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .is_err(), + true + ); - // Attempt force push, - // should work as it forces the push through - assert_eq!( - push( - tmp_other_repo_dir.path().to_str().unwrap(), - "origin", - "master", - true, - false, - None, - None, - ) - .is_err(), - false - ); - } + // Attempt force push, + // should work as it forces the push through + assert_eq!( + push( + tmp_other_repo_dir.path().to_str().unwrap(), + "origin", + "master", + true, + false, + None, + None, + ) + .is_err(), + false + ); + } - #[test] - fn test_force_push_rewrites_history() { - // This test mimics the scenario of 2 people having 2 - // local branches and both modifying the same file then - // both pushing, sequentially + #[test] + fn test_force_push_rewrites_history() { + // This test mimics the scenario of 2 people having 2 + // local branches and both modifying the same file then + // both pushing, sequentially - let (tmp_repo_dir, repo) = repo_init().unwrap(); - let (tmp_other_repo_dir, other_repo) = repo_init().unwrap(); - let (tmp_upstream_dir, upstream) = repo_init_bare().unwrap(); + let (tmp_repo_dir, repo) = repo_init().unwrap(); + let (tmp_other_repo_dir, other_repo) = repo_init().unwrap(); + let (tmp_upstream_dir, upstream) = repo_init_bare().unwrap(); - repo.remote( - "origin", - tmp_upstream_dir.path().to_str().unwrap(), - ) - .unwrap(); + repo.remote( + "origin", + tmp_upstream_dir.path().to_str().unwrap(), + ) + .unwrap(); - other_repo - .remote( - "origin", - tmp_upstream_dir.path().to_str().unwrap(), - ) - .unwrap(); + other_repo + .remote( + "origin", + tmp_upstream_dir.path().to_str().unwrap(), + ) + .unwrap(); - let tmp_repo_file_path = - tmp_repo_dir.path().join("temp_file.txt"); - let mut tmp_repo_file = - File::create(tmp_repo_file_path).unwrap(); - writeln!(tmp_repo_file, "TempSomething").unwrap(); + let tmp_repo_file_path = + tmp_repo_dir.path().join("temp_file.txt"); + let mut tmp_repo_file = + File::create(tmp_repo_file_path).unwrap(); + writeln!(tmp_repo_file, "TempSomething").unwrap(); - sync::stage_add_file( - tmp_repo_dir.path().to_str().unwrap(), - Path::new("temp_file.txt"), - ) - .unwrap(); + sync::stage_add_file( + tmp_repo_dir.path().to_str().unwrap(), + Path::new("temp_file.txt"), + ) + .unwrap(); - let repo_1_commit = sync::commit( - tmp_repo_dir.path().to_str().unwrap(), - "repo_1_commit", - ) - .unwrap(); + let repo_1_commit = sync::commit( + tmp_repo_dir.path().to_str().unwrap(), + "repo_1_commit", + ) + .unwrap(); - //NOTE: make sure the commit actually contains that file - assert_eq!( - sync::get_commit_files( - tmp_repo_dir.path().to_str().unwrap(), - repo_1_commit - ) - .unwrap()[0] - .path, - String::from("temp_file.txt") - ); + //NOTE: make sure the commit actually contains that file + assert_eq!( + sync::get_commit_files( + tmp_repo_dir.path().to_str().unwrap(), + repo_1_commit + ) + .unwrap()[0] + .path, + String::from("temp_file.txt") + ); - let commits = get_commit_ids(&repo, 1); - assert!(commits.contains(&repo_1_commit)); + let commits = get_commit_ids(&repo, 1); + assert!(commits.contains(&repo_1_commit)); - push( - tmp_repo_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + tmp_repo_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - let tmp_other_repo_file_path = - tmp_other_repo_dir.path().join("temp_file.txt"); - let mut tmp_other_repo_file = - File::create(tmp_other_repo_file_path).unwrap(); - writeln!(tmp_other_repo_file, "TempElse").unwrap(); + let tmp_other_repo_file_path = + tmp_other_repo_dir.path().join("temp_file.txt"); + let mut tmp_other_repo_file = + File::create(tmp_other_repo_file_path).unwrap(); + writeln!(tmp_other_repo_file, "TempElse").unwrap(); - sync::stage_add_file( - tmp_other_repo_dir.path().to_str().unwrap(), - Path::new("temp_file.txt"), - ) - .unwrap(); + sync::stage_add_file( + tmp_other_repo_dir.path().to_str().unwrap(), + Path::new("temp_file.txt"), + ) + .unwrap(); - let repo_2_commit = sync::commit( - tmp_other_repo_dir.path().to_str().unwrap(), - "repo_2_commit", - ) - .unwrap(); + let repo_2_commit = sync::commit( + tmp_other_repo_dir.path().to_str().unwrap(), + "repo_2_commit", + ) + .unwrap(); - let repo_2_parent = other_repo - .find_commit(repo_2_commit.into()) - .unwrap() - .parents() - .next() - .unwrap() - .id(); + let repo_2_parent = other_repo + .find_commit(repo_2_commit.into()) + .unwrap() + .parents() + .next() + .unwrap() + .id(); - let commits = get_commit_ids(&other_repo, 1); - assert!(commits.contains(&repo_2_commit)); + let commits = get_commit_ids(&other_repo, 1); + assert!(commits.contains(&repo_2_commit)); - // Attempt a normal push, - // should fail as branches diverged - assert_eq!( - push( - tmp_other_repo_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .is_err(), - true - ); + // Attempt a normal push, + // should fail as branches diverged + assert_eq!( + push( + tmp_other_repo_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .is_err(), + true + ); - // Check that the other commit is not in upstream, - // a normal push would not rewrite history - let commits = get_commit_ids(&upstream, 1); - assert!(!commits.contains(&repo_2_commit)); + // Check that the other commit is not in upstream, + // a normal push would not rewrite history + let commits = get_commit_ids(&upstream, 1); + assert!(!commits.contains(&repo_2_commit)); - // Attempt force push, - // should work as it forces the push through + // Attempt force push, + // should work as it forces the push through - push( - tmp_other_repo_dir.path().to_str().unwrap(), - "origin", - "master", - true, - false, - None, - None, - ) - .unwrap(); + push( + tmp_other_repo_dir.path().to_str().unwrap(), + "origin", + "master", + true, + false, + None, + None, + ) + .unwrap(); - let commits = get_commit_ids(&upstream, 1); - assert!(commits.contains(&repo_2_commit)); + let commits = get_commit_ids(&upstream, 1); + assert!(commits.contains(&repo_2_commit)); - let new_upstream_parent = - Repository::init_bare(tmp_upstream_dir.path()) - .unwrap() - .find_commit(repo_2_commit.into()) - .unwrap() - .parents() - .next() - .unwrap() - .id(); - assert_eq!(new_upstream_parent, repo_2_parent,); - } + let new_upstream_parent = + Repository::init_bare(tmp_upstream_dir.path()) + .unwrap() + .find_commit(repo_2_commit.into()) + .unwrap() + .parents() + .next() + .unwrap() + .id(); + assert_eq!(new_upstream_parent, repo_2_parent,); + } - #[test] - fn test_delete_remote_branch() { - // This test mimics the scenario of a user creating a branch, push it, and then remove it on the remote + #[test] + fn test_delete_remote_branch() { + // This test mimics the scenario of a user creating a branch, push it, and then remove it on the remote - let (upstream_dir, upstream_repo) = repo_init_bare().unwrap(); + let (upstream_dir, upstream_repo) = repo_init_bare().unwrap(); - let (tmp_repo_dir, repo) = - repo_clone(upstream_dir.path().to_str().unwrap()) - .unwrap(); + let (tmp_repo_dir, repo) = + repo_clone(upstream_dir.path().to_str().unwrap()) + .unwrap(); - // You need a commit before being able to branch ! - let commit_1 = write_commit_file( - &repo, - "temp_file.txt", - "SomeContent", - "Initial commit", - ); + // You need a commit before being able to branch ! + let commit_1 = write_commit_file( + &repo, + "temp_file.txt", + "SomeContent", + "Initial commit", + ); - let commits = get_commit_ids(&repo, 1); - assert!(commits.contains(&commit_1)); + let commits = get_commit_ids(&repo, 1); + assert!(commits.contains(&commit_1)); - push( - tmp_repo_dir.path().to_str().unwrap(), - "origin", - "master", - false, - false, - None, - None, - ) - .unwrap(); + push( + tmp_repo_dir.path().to_str().unwrap(), + "origin", + "master", + false, + false, + None, + None, + ) + .unwrap(); - // Create the local branch - sync::create_branch( - tmp_repo_dir.path().to_str().unwrap(), - "test_branch", - ) - .unwrap(); + // Create the local branch + sync::create_branch( + tmp_repo_dir.path().to_str().unwrap(), + "test_branch", + ) + .unwrap(); - // Push the local branch - push( - tmp_repo_dir.path().to_str().unwrap(), - "origin", - "test_branch", - false, - false, - None, - None, - ) - .unwrap(); + // Push the local branch + push( + tmp_repo_dir.path().to_str().unwrap(), + "origin", + "test_branch", + false, + false, + None, + None, + ) + .unwrap(); - // Test if the branch exits on the remote - assert_eq!( - upstream_repo - .branches(None) - .unwrap() - .map(|i| i.unwrap()) - .map(|(i, _)| i.name().unwrap().unwrap().to_string()) - .filter(|i| i == "test_branch") - .next() - .is_some(), - true - ); + // Test if the branch exits on the remote + assert_eq!( + upstream_repo + .branches(None) + .unwrap() + .map(|i| i.unwrap()) + .map(|(i, _)| i.name().unwrap().unwrap().to_string()) + .filter(|i| i == "test_branch") + .next() + .is_some(), + true + ); - // Delete the remote branch - assert_eq!( - push( - tmp_repo_dir.path().to_str().unwrap(), - "origin", - "test_branch", - false, - true, - None, - None, - ) - .is_ok(), - true - ); + // Delete the remote branch + assert_eq!( + push( + tmp_repo_dir.path().to_str().unwrap(), + "origin", + "test_branch", + false, + true, + None, + None, + ) + .is_ok(), + true + ); - // Test that the branch has be remove from the remote - assert_eq!( - upstream_repo - .branches(None) - .unwrap() - .map(|i| i.unwrap()) - .map(|(i, _)| i.name().unwrap().unwrap().to_string()) - .filter(|i| i == "test_branch") - .next() - .is_some(), - false - ); - } + // Test that the branch has be remove from the remote + assert_eq!( + upstream_repo + .branches(None) + .unwrap() + .map(|i| i.unwrap()) + .map(|(i, _)| i.name().unwrap().unwrap().to_string()) + .filter(|i| i == "test_branch") + .next() + .is_some(), + false + ); + } } diff --git a/asyncgit/src/sync/remotes/tags.rs b/asyncgit/src/sync/remotes/tags.rs index 0af6dd0f..51905997 100644 --- a/asyncgit/src/sync/remotes/tags.rs +++ b/asyncgit/src/sync/remotes/tags.rs @@ -2,9 +2,9 @@ use super::{push::AsyncProgress, utils}; use crate::{ - error::Result, - progress::ProgressPercent, - sync::{cred::BasicAuthCredential, remotes::Callbacks}, + error::Result, + progress::ProgressPercent, + sync::{cred::BasicAuthCredential, remotes::Callbacks}, }; use crossbeam_channel::Sender; use git2::{Direction, PushOptions}; @@ -14,263 +14,263 @@ use std::collections::HashSet; /// #[derive(Debug, Copy, Clone, PartialEq)] pub enum PushTagsProgress { - /// fetching tags from remote to check which local tags need pushing - CheckRemote, - /// pushing local tags that are missing remote - Push { - /// - pushed: usize, - /// - total: usize, - }, - /// done - Done, + /// fetching tags from remote to check which local tags need pushing + CheckRemote, + /// pushing local tags that are missing remote + Push { + /// + pushed: usize, + /// + total: usize, + }, + /// done + Done, } impl AsyncProgress for PushTagsProgress { - fn progress(&self) -> ProgressPercent { - match self { - Self::CheckRemote => ProgressPercent::empty(), - Self::Push { pushed, total } => { - ProgressPercent::new(*pushed, *total) - } - Self::Done => ProgressPercent::full(), - } - } - fn is_done(&self) -> bool { - *self == Self::Done - } + fn progress(&self) -> ProgressPercent { + match self { + Self::CheckRemote => ProgressPercent::empty(), + Self::Push { pushed, total } => { + ProgressPercent::new(*pushed, *total) + } + Self::Done => ProgressPercent::full(), + } + } + fn is_done(&self) -> bool { + *self == Self::Done + } } /// lists the remotes tags fn remote_tag_refs( - repo_path: &str, - remote: &str, - basic_credential: Option, + repo_path: &str, + remote: &str, + basic_credential: Option, ) -> Result> { - scope_time!("remote_tags"); + scope_time!("remote_tags"); - let repo = utils::repo(repo_path)?; - let mut remote = repo.find_remote(remote)?; - let callbacks = Callbacks::new(None, basic_credential); - let conn = remote.connect_auth( - Direction::Fetch, - Some(callbacks.callbacks()), - None, - )?; + let repo = utils::repo(repo_path)?; + let mut remote = repo.find_remote(remote)?; + let callbacks = Callbacks::new(None, basic_credential); + let conn = remote.connect_auth( + Direction::Fetch, + Some(callbacks.callbacks()), + None, + )?; - let remote_heads = conn.list()?; - let remote_tags = remote_heads - .iter() - .map(|s| s.name().to_string()) - .filter(|name| { - name.starts_with("refs/tags/") && !name.ends_with("^{}") - }) - .collect::>(); + let remote_heads = conn.list()?; + let remote_tags = remote_heads + .iter() + .map(|s| s.name().to_string()) + .filter(|name| { + name.starts_with("refs/tags/") && !name.ends_with("^{}") + }) + .collect::>(); - Ok(remote_tags) + Ok(remote_tags) } /// lists the remotes tags missing pub fn tags_missing_remote( - repo_path: &str, - remote: &str, - basic_credential: Option, + repo_path: &str, + remote: &str, + basic_credential: Option, ) -> Result> { - scope_time!("tags_missing_remote"); + scope_time!("tags_missing_remote"); - let repo = utils::repo(repo_path)?; - let tags = repo.tag_names(None)?; + let repo = utils::repo(repo_path)?; + let tags = repo.tag_names(None)?; - let mut local_tags = tags - .iter() - .filter_map(|tag| tag.map(|tag| format!("refs/tags/{}", tag))) - .collect::>(); - let remote_tags = - remote_tag_refs(repo_path, remote, basic_credential)?; + let mut local_tags = tags + .iter() + .filter_map(|tag| tag.map(|tag| format!("refs/tags/{}", tag))) + .collect::>(); + let remote_tags = + remote_tag_refs(repo_path, remote, basic_credential)?; - for t in remote_tags { - local_tags.remove(&t); - } + for t in remote_tags { + local_tags.remove(&t); + } - Ok(local_tags.into_iter().collect()) + Ok(local_tags.into_iter().collect()) } /// pub fn push_tags( - repo_path: &str, - remote: &str, - basic_credential: Option, - progress_sender: Option>, + repo_path: &str, + remote: &str, + basic_credential: Option, + progress_sender: Option>, ) -> Result<()> { - scope_time!("push_tags"); + scope_time!("push_tags"); - progress_sender - .as_ref() - .map(|sender| sender.send(PushTagsProgress::CheckRemote)); + progress_sender + .as_ref() + .map(|sender| sender.send(PushTagsProgress::CheckRemote)); - let tags_missing = tags_missing_remote( - repo_path, - remote, - basic_credential.clone(), - )?; + let tags_missing = tags_missing_remote( + repo_path, + remote, + basic_credential.clone(), + )?; - let repo = utils::repo(repo_path)?; - let mut remote = repo.find_remote(remote)?; + let repo = utils::repo(repo_path)?; + let mut remote = repo.find_remote(remote)?; - let total = tags_missing.len(); + let total = tags_missing.len(); - progress_sender.as_ref().map(|sender| { - sender.send(PushTagsProgress::Push { pushed: 0, total }) - }); + progress_sender.as_ref().map(|sender| { + sender.send(PushTagsProgress::Push { pushed: 0, total }) + }); - for (idx, tag) in tags_missing.into_iter().enumerate() { - let mut options = PushOptions::new(); - let callbacks = - Callbacks::new(None, basic_credential.clone()); - options.remote_callbacks(callbacks.callbacks()); - options.packbuilder_parallelism(0); - remote.push(&[tag.as_str()], Some(&mut options))?; + for (idx, tag) in tags_missing.into_iter().enumerate() { + let mut options = PushOptions::new(); + let callbacks = + Callbacks::new(None, basic_credential.clone()); + options.remote_callbacks(callbacks.callbacks()); + options.packbuilder_parallelism(0); + remote.push(&[tag.as_str()], Some(&mut options))?; - progress_sender.as_ref().map(|sender| { - sender.send(PushTagsProgress::Push { - pushed: idx + 1, - total, - }) - }); - } + progress_sender.as_ref().map(|sender| { + sender.send(PushTagsProgress::Push { + pushed: idx + 1, + total, + }) + }); + } - drop(basic_credential); + drop(basic_credential); - progress_sender.map(|sender| sender.send(PushTagsProgress::Done)); + progress_sender.map(|sender| sender.send(PushTagsProgress::Done)); - Ok(()) + Ok(()) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::{ - self, - remotes::{fetch, push::push}, - tests::{repo_clone, repo_init_bare}, - }; - use sync::tests::write_commit_file; + use super::*; + use crate::sync::{ + self, + remotes::{fetch, push::push}, + tests::{repo_clone, repo_init_bare}, + }; + use sync::tests::write_commit_file; - #[test] - fn test_push_pull_tags() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); - let r1_dir = r1_dir.path().to_str().unwrap(); + #[test] + fn test_push_pull_tags() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); + let r1_dir = r1_dir.path().to_str().unwrap(); - let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap(); + let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - let (clone2_dir, clone2) = repo_clone(r1_dir).unwrap(); + let (clone2_dir, clone2) = repo_clone(r1_dir).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); + let clone2_dir = clone2_dir.path().to_str().unwrap(); - // clone1 + // clone1 - let commit1 = - write_commit_file(&clone1, "test.txt", "test", "commit1"); + let commit1 = + write_commit_file(&clone1, "test.txt", "test", "commit1"); - sync::tag(clone1_dir, &commit1, "tag1").unwrap(); + sync::tag(clone1_dir, &commit1, "tag1").unwrap(); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); - push_tags(clone1_dir, "origin", None, None).unwrap(); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); + push_tags(clone1_dir, "origin", None, None).unwrap(); - // clone2 + // clone2 - let _commit2 = write_commit_file( - &clone2, - "test2.txt", - "test", - "commit2", - ); + let _commit2 = write_commit_file( + &clone2, + "test2.txt", + "test", + "commit2", + ); - assert_eq!(sync::get_tags(clone2_dir).unwrap().len(), 0); + assert_eq!(sync::get_tags(clone2_dir).unwrap().len(), 0); - //lets fetch from origin - let bytes = fetch(clone2_dir, "master", None, None).unwrap(); - assert!(bytes > 0); + //lets fetch from origin + let bytes = fetch(clone2_dir, "master", None, None).unwrap(); + assert!(bytes > 0); - sync::merge_upstream_commit(clone2_dir, "master").unwrap(); + sync::merge_upstream_commit(clone2_dir, "master").unwrap(); - assert_eq!(sync::get_tags(clone2_dir).unwrap().len(), 1); - } + assert_eq!(sync::get_tags(clone2_dir).unwrap().len(), 1); + } - #[test] - fn test_get_remote_tags() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); - let r1_dir = r1_dir.path().to_str().unwrap(); + #[test] + fn test_get_remote_tags() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); + let r1_dir = r1_dir.path().to_str().unwrap(); - let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap(); + let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - let (clone2_dir, _clone2) = repo_clone(r1_dir).unwrap(); + let (clone2_dir, _clone2) = repo_clone(r1_dir).unwrap(); - let clone2_dir = clone2_dir.path().to_str().unwrap(); + let clone2_dir = clone2_dir.path().to_str().unwrap(); - // clone1 + // clone1 - let commit1 = - write_commit_file(&clone1, "test.txt", "test", "commit1"); + let commit1 = + write_commit_file(&clone1, "test.txt", "test", "commit1"); - sync::tag(clone1_dir, &commit1, "tag1").unwrap(); + sync::tag(clone1_dir, &commit1, "tag1").unwrap(); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); - push_tags(clone1_dir, "origin", None, None).unwrap(); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); + push_tags(clone1_dir, "origin", None, None).unwrap(); - // clone2 + // clone2 - let tags = - remote_tag_refs(clone2_dir, "origin", None).unwrap(); + let tags = + remote_tag_refs(clone2_dir, "origin", None).unwrap(); - assert_eq!( - tags.as_slice(), - &[String::from("refs/tags/tag1")] - ); - } + assert_eq!( + tags.as_slice(), + &[String::from("refs/tags/tag1")] + ); + } - #[test] - fn test_tags_missing_remote() { - let (r1_dir, _repo) = repo_init_bare().unwrap(); - let r1_dir = r1_dir.path().to_str().unwrap(); + #[test] + fn test_tags_missing_remote() { + let (r1_dir, _repo) = repo_init_bare().unwrap(); + let r1_dir = r1_dir.path().to_str().unwrap(); - let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap(); + let (clone1_dir, clone1) = repo_clone(r1_dir).unwrap(); - let clone1_dir = clone1_dir.path().to_str().unwrap(); + let clone1_dir = clone1_dir.path().to_str().unwrap(); - // clone1 + // clone1 - let commit1 = - write_commit_file(&clone1, "test.txt", "test", "commit1"); + let commit1 = + write_commit_file(&clone1, "test.txt", "test", "commit1"); - sync::tag(clone1_dir, &commit1, "tag1").unwrap(); + sync::tag(clone1_dir, &commit1, "tag1").unwrap(); - push( - clone1_dir, "origin", "master", false, false, None, None, - ) - .unwrap(); + push( + clone1_dir, "origin", "master", false, false, None, None, + ) + .unwrap(); - let tags_missing = - tags_missing_remote(clone1_dir, "origin", None).unwrap(); + let tags_missing = + tags_missing_remote(clone1_dir, "origin", None).unwrap(); - assert_eq!( - tags_missing.as_slice(), - &[String::from("refs/tags/tag1")] - ); - push_tags(clone1_dir, "origin", None, None).unwrap(); - let tags_missing = - tags_missing_remote(clone1_dir, "origin", None).unwrap(); - assert!(tags_missing.is_empty()); - } + assert_eq!( + tags_missing.as_slice(), + &[String::from("refs/tags/tag1")] + ); + push_tags(clone1_dir, "origin", None, None).unwrap(); + let tags_missing = + tags_missing_remote(clone1_dir, "origin", None).unwrap(); + assert!(tags_missing.is_empty()); + } } diff --git a/asyncgit/src/sync/reset.rs b/asyncgit/src/sync/reset.rs index 1d414ea0..c1b8c456 100644 --- a/asyncgit/src/sync/reset.rs +++ b/asyncgit/src/sync/reset.rs @@ -5,58 +5,58 @@ use scopetime::scope_time; /// pub fn reset_stage(repo_path: &str, path: &str) -> Result<()> { - scope_time!("reset_stage"); + scope_time!("reset_stage"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - if let Ok(id) = get_head_repo(&repo) { - let obj = - repo.find_object(id.into(), Some(ObjectType::Commit))?; + if let Ok(id) = get_head_repo(&repo) { + let obj = + repo.find_object(id.into(), Some(ObjectType::Commit))?; - repo.reset_default(Some(&obj), &[path])?; - } else { - repo.reset_default(None, &[path])?; - } + repo.reset_default(Some(&obj), &[path])?; + } else { + repo.reset_default(None, &[path])?; + } - Ok(()) + Ok(()) } /// pub fn reset_workdir(repo_path: &str, path: &str) -> Result<()> { - scope_time!("reset_workdir"); + scope_time!("reset_workdir"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let mut checkout_opts = CheckoutBuilder::new(); - checkout_opts - .update_index(true) // windows: needs this to be true WTF?! - .remove_untracked(true) - .force() - .path(path); + let mut checkout_opts = CheckoutBuilder::new(); + checkout_opts + .update_index(true) // windows: needs this to be true WTF?! + .remove_untracked(true) + .force() + .path(path); - repo.checkout_index(None, Some(&mut checkout_opts))?; - Ok(()) + repo.checkout_index(None, Some(&mut checkout_opts))?; + Ok(()) } #[cfg(test)] mod tests { - use super::{reset_stage, reset_workdir}; - use crate::error::Result; - use crate::sync::{ - commit, - status::{get_status, StatusType}, - tests::{ - debug_cmd_print, get_statuses, repo_init, repo_init_empty, - }, - utils::{stage_add_all, stage_add_file}, - }; - use std::{ - fs::{self, File}, - io::Write, - path::Path, - }; + use super::{reset_stage, reset_workdir}; + use crate::error::Result; + use crate::sync::{ + commit, + status::{get_status, StatusType}, + tests::{ + debug_cmd_print, get_statuses, repo_init, repo_init_empty, + }, + utils::{stage_add_all, stage_add_file}, + }; + use std::{ + fs::{self, File}, + io::Write, + path::Path, + }; - static HUNK_A: &str = r" + static HUNK_A: &str = r" 1 start 2 3 @@ -69,7 +69,7 @@ mod tests { 0 1 end"; - static HUNK_B: &str = r" + static HUNK_B: &str = r" 1 start 2 newa 3 @@ -82,233 +82,233 @@ mod tests { 0 newb 1 end"; - #[test] - fn test_reset_only_unstaged() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_reset_only_unstaged() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let res = - get_status(repo_path, StatusType::WorkingDir).unwrap(); - assert_eq!(res.len(), 0); + let res = + get_status(repo_path, StatusType::WorkingDir).unwrap(); + assert_eq!(res.len(), 0); - let file_path = root.join("bar.txt"); + let file_path = root.join("bar.txt"); - { - File::create(&file_path) - .unwrap() - .write_all(HUNK_A.as_bytes()) - .unwrap(); - } + { + File::create(&file_path) + .unwrap() + .write_all(HUNK_A.as_bytes()) + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - stage_add_file(repo_path, Path::new("bar.txt")).unwrap(); + stage_add_file(repo_path, Path::new("bar.txt")).unwrap(); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - // overwrite with next content - { - File::create(&file_path) - .unwrap() - .write_all(HUNK_B.as_bytes()) - .unwrap(); - } + // overwrite with next content + { + File::create(&file_path) + .unwrap() + .write_all(HUNK_B.as_bytes()) + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (1, 1)); + assert_eq!(get_statuses(repo_path), (1, 1)); - reset_workdir(repo_path, "bar.txt").unwrap(); + reset_workdir(repo_path, "bar.txt").unwrap(); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (0, 1)); - } + assert_eq!(get_statuses(repo_path), (0, 1)); + } - #[test] - fn test_reset_untracked_in_subdir() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_reset_untracked_in_subdir() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - { - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar.txt")) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); - } + { + fs::create_dir(&root.join("foo")).unwrap(); + File::create(&root.join("foo/bar.txt")) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - reset_workdir(repo_path, "foo/bar.txt").unwrap(); + reset_workdir(repo_path, "foo/bar.txt").unwrap(); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (0, 0)); - } + assert_eq!(get_statuses(repo_path), (0, 0)); + } - #[test] - fn test_reset_folder() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_reset_folder() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - { - fs::create_dir(&root.join("foo"))?; - File::create(&root.join("foo/file1.txt"))? - .write_all(b"file1")?; - File::create(&root.join("foo/file2.txt"))? - .write_all(b"file1")?; - File::create(&root.join("file3.txt"))? - .write_all(b"file3")?; - } + { + fs::create_dir(&root.join("foo"))?; + File::create(&root.join("foo/file1.txt"))? + .write_all(b"file1")?; + File::create(&root.join("foo/file2.txt"))? + .write_all(b"file1")?; + File::create(&root.join("file3.txt"))? + .write_all(b"file3")?; + } - stage_add_all(repo_path, "*").unwrap(); - commit(repo_path, "msg").unwrap(); + stage_add_all(repo_path, "*").unwrap(); + commit(repo_path, "msg").unwrap(); - { - File::create(&root.join("foo/file1.txt"))? - .write_all(b"file1\nadded line")?; - fs::remove_file(&root.join("foo/file2.txt"))?; - File::create(&root.join("foo/file4.txt"))? - .write_all(b"file4")?; - File::create(&root.join("foo/file5.txt"))? - .write_all(b"file5")?; - File::create(&root.join("file3.txt"))? - .write_all(b"file3\nadded line")?; - } + { + File::create(&root.join("foo/file1.txt"))? + .write_all(b"file1\nadded line")?; + fs::remove_file(&root.join("foo/file2.txt"))?; + File::create(&root.join("foo/file4.txt"))? + .write_all(b"file4")?; + File::create(&root.join("foo/file5.txt"))? + .write_all(b"file5")?; + File::create(&root.join("file3.txt"))? + .write_all(b"file3\nadded line")?; + } - assert_eq!(get_statuses(repo_path), (5, 0)); + assert_eq!(get_statuses(repo_path), (5, 0)); - stage_add_file(repo_path, Path::new("foo/file5.txt")) - .unwrap(); + stage_add_file(repo_path, Path::new("foo/file5.txt")) + .unwrap(); - assert_eq!(get_statuses(repo_path), (4, 1)); + assert_eq!(get_statuses(repo_path), (4, 1)); - reset_workdir(repo_path, "foo").unwrap(); + reset_workdir(repo_path, "foo").unwrap(); - assert_eq!(get_statuses(repo_path), (1, 1)); + assert_eq!(get_statuses(repo_path), (1, 1)); - Ok(()) - } + Ok(()) + } - #[test] - fn test_reset_untracked_in_subdir_and_index() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); - let file = "foo/bar.txt"; + #[test] + fn test_reset_untracked_in_subdir_and_index() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); + let file = "foo/bar.txt"; - { - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join(file)) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); - } + { + fs::create_dir(&root.join("foo")).unwrap(); + File::create(&root.join(file)) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - debug_cmd_print(repo_path, "git add ."); + debug_cmd_print(repo_path, "git add ."); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - { - File::create(&root.join(file)) - .unwrap() - .write_all(b"test\nfoo\nnewend") - .unwrap(); - } + { + File::create(&root.join(file)) + .unwrap() + .write_all(b"test\nfoo\nnewend") + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (1, 1)); + assert_eq!(get_statuses(repo_path), (1, 1)); - reset_workdir(repo_path, file).unwrap(); + reset_workdir(repo_path, file).unwrap(); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (0, 1)); - } + assert_eq!(get_statuses(repo_path), (0, 1)); + } - #[test] - fn unstage_in_empty_repo() { - let file_path = Path::new("foo.txt"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn unstage_in_empty_repo() { + let file_path = Path::new("foo.txt"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path)) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); + File::create(&root.join(file_path)) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - assert_eq!(get_statuses(repo_path), (0, 1)); + assert_eq!(get_statuses(repo_path), (0, 1)); - reset_stage(repo_path, file_path.to_str().unwrap()).unwrap(); + reset_stage(repo_path, file_path.to_str().unwrap()).unwrap(); - assert_eq!(get_statuses(repo_path), (1, 0)); - } + assert_eq!(get_statuses(repo_path), (1, 0)); + } - #[test] - fn test_reset_untracked_in_subdir_with_cwd_in_subdir() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_reset_untracked_in_subdir_with_cwd_in_subdir() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - { - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar.txt")) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); - } + { + fs::create_dir(&root.join("foo")).unwrap(); + File::create(&root.join("foo/bar.txt")) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - reset_workdir( - &root.join("foo").as_os_str().to_str().unwrap(), - "foo/bar.txt", - ) - .unwrap(); + reset_workdir( + &root.join("foo").as_os_str().to_str().unwrap(), + "foo/bar.txt", + ) + .unwrap(); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (0, 0)); - } + assert_eq!(get_statuses(repo_path), (0, 0)); + } - #[test] - fn test_reset_untracked_subdir() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_reset_untracked_subdir() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - { - fs::create_dir_all(&root.join("foo/bar")).unwrap(); - File::create(&root.join("foo/bar/baz.txt")) - .unwrap() - .write_all(b"test\nfoo") - .unwrap(); - } + { + fs::create_dir_all(&root.join("foo/bar")).unwrap(); + File::create(&root.join("foo/bar/baz.txt")) + .unwrap() + .write_all(b"test\nfoo") + .unwrap(); + } - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - reset_workdir(repo_path, "foo/bar").unwrap(); + reset_workdir(repo_path, "foo/bar").unwrap(); - debug_cmd_print(repo_path, "git status"); + debug_cmd_print(repo_path, "git status"); - assert_eq!(get_statuses(repo_path), (0, 0)); - } + assert_eq!(get_statuses(repo_path), (0, 0)); + } } diff --git a/asyncgit/src/sync/staging/discard_tracked.rs b/asyncgit/src/sync/staging/discard_tracked.rs index 4adda5b5..bc10bfd1 100644 --- a/asyncgit/src/sync/staging/discard_tracked.rs +++ b/asyncgit/src/sync/staging/discard_tracked.rs @@ -1,344 +1,344 @@ use super::{apply_selection, load_file}; use crate::error::Result; use crate::sync::{ - diff::DiffLinePosition, - patches::get_file_diff_patch_and_hunklines, - utils::{repo, repo_write_file}, + diff::DiffLinePosition, + patches::get_file_diff_patch_and_hunklines, + utils::{repo, repo_write_file}, }; use scopetime::scope_time; /// discards specific lines in an unstaged hunk of a diff pub fn discard_lines( - repo_path: &str, - file_path: &str, - lines: &[DiffLinePosition], + repo_path: &str, + file_path: &str, + lines: &[DiffLinePosition], ) -> Result<()> { - scope_time!("discard_lines"); + scope_time!("discard_lines"); - if lines.is_empty() { - return Ok(()); - } + if lines.is_empty() { + return Ok(()); + } - let repo = repo(repo_path)?; - repo.index()?.read(true)?; + let repo = repo(repo_path)?; + repo.index()?.read(true)?; - //TODO: check that file is not new (status modified) + //TODO: check that file is not new (status modified) - let new_content = { - let (_patch, hunks) = get_file_diff_patch_and_hunklines( - &repo, file_path, false, false, - )?; + let new_content = { + let (_patch, hunks) = get_file_diff_patch_and_hunklines( + &repo, file_path, false, false, + )?; - let working_content = load_file(&repo, file_path)?; - let old_lines = working_content.lines().collect::>(); + let working_content = load_file(&repo, file_path)?; + let old_lines = working_content.lines().collect::>(); - apply_selection(lines, &hunks, &old_lines, false, true)? - }; + apply_selection(lines, &hunks, &old_lines, false, true)? + }; - repo_write_file(&repo, file_path, new_content.as_str())?; + repo_write_file(&repo, file_path, new_content.as_str())?; - Ok(()) + Ok(()) } #[cfg(test)] mod test { - use super::*; - use crate::sync::tests::{repo_init, write_commit_file}; + use super::*; + use crate::sync::tests::{repo_init, write_commit_file}; - #[test] - fn test_discard() { - static FILE_1: &str = r"0 + #[test] + fn test_discard() { + static FILE_1: &str = r"0 1 2 3 4 "; - static FILE_2: &str = r"0 + static FILE_2: &str = r"0 3 4 "; - static FILE_3: &str = r"0 + static FILE_3: &str = r"0 2 3 4 "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[ - DiffLinePosition { - old_lineno: Some(3), - new_lineno: None, - }, - DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }, - ], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[ + DiffLinePosition { + old_lineno: Some(3), + new_lineno: None, + }, + DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }, + ], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } - #[test] - fn test_discard2() { - static FILE_1: &str = r"start + #[test] + fn test_discard2() { + static FILE_1: &str = r"start end "; - static FILE_2: &str = r"start + static FILE_2: &str = r"start 1 2 end "; - static FILE_3: &str = r"start + static FILE_3: &str = r"start 1 end "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[DiffLinePosition { - old_lineno: None, - new_lineno: Some(3), - }], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[DiffLinePosition { + old_lineno: None, + new_lineno: Some(3), + }], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } - #[test] - fn test_discard3() { - static FILE_1: &str = r"start + #[test] + fn test_discard3() { + static FILE_1: &str = r"start 1 end "; - static FILE_2: &str = r"start + static FILE_2: &str = r"start 2 end "; - static FILE_3: &str = r"start + static FILE_3: &str = r"start 1 end "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[ - DiffLinePosition { - old_lineno: Some(2), - new_lineno: None, - }, - DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }, - ], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[ + DiffLinePosition { + old_lineno: Some(2), + new_lineno: None, + }, + DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }, + ], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } - #[test] - fn test_discard4() { - static FILE_1: &str = r"start + #[test] + fn test_discard4() { + static FILE_1: &str = r"start mid end "; - static FILE_2: &str = r"start + static FILE_2: &str = r"start 1 mid 2 end "; - static FILE_3: &str = r"start + static FILE_3: &str = r"start mid end "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[ - DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }, - DiffLinePosition { - old_lineno: None, - new_lineno: Some(4), - }, - ], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[ + DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }, + DiffLinePosition { + old_lineno: None, + new_lineno: Some(4), + }, + ], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } - #[test] - fn test_discard_if_first_selected_line_is_not_in_any_hunk() { - static FILE_1: &str = r"start + #[test] + fn test_discard_if_first_selected_line_is_not_in_any_hunk() { + static FILE_1: &str = r"start end "; - static FILE_2: &str = r"start + static FILE_2: &str = r"start 1 end "; - static FILE_3: &str = r"start + static FILE_3: &str = r"start end "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[ - DiffLinePosition { - old_lineno: None, - new_lineno: Some(1), - }, - DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }, - ], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[ + DiffLinePosition { + old_lineno: None, + new_lineno: Some(1), + }, + DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }, + ], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } - //this test shows that we require at least a diff context around add/removes of 1 - #[test] - fn test_discard_deletions_filestart_breaking_with_zero_context() { - static FILE_1: &str = r"start + //this test shows that we require at least a diff context around add/removes of 1 + #[test] + fn test_discard_deletions_filestart_breaking_with_zero_context() { + static FILE_1: &str = r"start mid end "; - static FILE_2: &str = r"start + static FILE_2: &str = r"start end "; - static FILE_3: &str = r"start + static FILE_3: &str = r"start mid end "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[DiffLinePosition { - old_lineno: Some(2), - new_lineno: None, - }], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[DiffLinePosition { + old_lineno: Some(2), + new_lineno: None, + }], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } - #[test] - fn test_discard5() { - static FILE_1: &str = r"start + #[test] + fn test_discard5() { + static FILE_1: &str = r"start "; - static FILE_2: &str = r"start + static FILE_2: &str = r"start 1"; - static FILE_3: &str = r"start + static FILE_3: &str = r"start "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - discard_lines( - path, - "test.txt", - &[DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }], - ) - .unwrap(); + discard_lines( + path, + "test.txt", + &[DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }], + ) + .unwrap(); - let result_file = load_file(&repo, "test.txt").unwrap(); + let result_file = load_file(&repo, "test.txt").unwrap(); - assert_eq!(result_file.as_str(), FILE_3); - } + assert_eq!(result_file.as_str(), FILE_3); + } } diff --git a/asyncgit/src/sync/staging/mod.rs b/asyncgit/src/sync/staging/mod.rs index e2830020..1621c9d4 100644 --- a/asyncgit/src/sync/staging/mod.rs +++ b/asyncgit/src/sync/staging/mod.rs @@ -5,182 +5,182 @@ pub use discard_tracked::discard_lines; pub use stage_tracked::stage_lines; use super::{ - diff::DiffLinePosition, patches::HunkLines, utils::work_dir, + diff::DiffLinePosition, patches::HunkLines, utils::work_dir, }; use crate::error::Result; use git2::{DiffLine, DiffLineType, Repository}; use std::{ - collections::HashSet, convert::TryFrom, fs::File, io::Read, + collections::HashSet, convert::TryFrom, fs::File, io::Read, }; const NEWLINE: char = '\n'; #[derive(Default)] struct NewFromOldContent { - lines: Vec, - old_index: usize, + lines: Vec, + old_index: usize, } impl NewFromOldContent { - fn add_from_hunk(&mut self, line: &DiffLine) -> Result<()> { - let line = String::from_utf8(line.content().into())?; + fn add_from_hunk(&mut self, line: &DiffLine) -> Result<()> { + let line = String::from_utf8(line.content().into())?; - let line = if line.ends_with(NEWLINE) { - line[0..line.len() - 1].to_string() - } else { - line - }; + let line = if line.ends_with(NEWLINE) { + line[0..line.len() - 1].to_string() + } else { + line + }; - self.lines.push(line); + self.lines.push(line); - Ok(()) - } + Ok(()) + } - fn skip_old_line(&mut self) { - self.old_index += 1; - } + fn skip_old_line(&mut self) { + self.old_index += 1; + } - fn add_old_line(&mut self, old_lines: &[&str]) { - self.lines.push(old_lines[self.old_index].to_string()); - self.old_index += 1; - } + fn add_old_line(&mut self, old_lines: &[&str]) { + self.lines.push(old_lines[self.old_index].to_string()); + self.old_index += 1; + } - fn catchup_to_hunkstart( - &mut self, - hunk_start: usize, - old_lines: &[&str], - ) { - while hunk_start > self.old_index + 1 { - self.add_old_line(old_lines); - } - } + fn catchup_to_hunkstart( + &mut self, + hunk_start: usize, + old_lines: &[&str], + ) { + while hunk_start > self.old_index + 1 { + self.add_old_line(old_lines); + } + } - fn finish(mut self, old_lines: &[&str]) -> String { - for line in old_lines.iter().skip(self.old_index) { - self.lines.push((*line).to_string()); - } - let lines = self.lines.join("\n"); - if lines.ends_with(NEWLINE) { - lines - } else { - let mut lines = lines; - lines.push(NEWLINE); - lines - } - } + fn finish(mut self, old_lines: &[&str]) -> String { + for line in old_lines.iter().skip(self.old_index) { + self.lines.push((*line).to_string()); + } + let lines = self.lines.join("\n"); + if lines.ends_with(NEWLINE) { + lines + } else { + let mut lines = lines; + lines.push(NEWLINE); + lines + } + } } // this is the heart of the per line discard,stage,unstage. heavily inspired by the great work in nodegit: https://github.com/nodegit/nodegit #[allow(clippy::redundant_pub_crate)] pub(crate) fn apply_selection( - lines: &[DiffLinePosition], - hunks: &[HunkLines], - old_lines: &[&str], - is_staged: bool, - reverse: bool, + lines: &[DiffLinePosition], + hunks: &[HunkLines], + old_lines: &[&str], + is_staged: bool, + reverse: bool, ) -> Result { - let mut new_content = NewFromOldContent::default(); - let lines = lines.iter().collect::>(); + let mut new_content = NewFromOldContent::default(); + let lines = lines.iter().collect::>(); - let added = if reverse { - DiffLineType::Deletion - } else { - DiffLineType::Addition - }; - let deleted = if reverse { - DiffLineType::Addition - } else { - DiffLineType::Deletion - }; + let added = if reverse { + DiffLineType::Deletion + } else { + DiffLineType::Addition + }; + let deleted = if reverse { + DiffLineType::Addition + } else { + DiffLineType::Deletion + }; - let mut first_hunk_encountered = false; - for hunk in hunks { - let hunk_start = if is_staged || reverse { - usize::try_from(hunk.hunk.new_start)? - } else { - usize::try_from(hunk.hunk.old_start)? - }; + let mut first_hunk_encountered = false; + for hunk in hunks { + let hunk_start = if is_staged || reverse { + usize::try_from(hunk.hunk.new_start)? + } else { + usize::try_from(hunk.hunk.old_start)? + }; - if !first_hunk_encountered { - let any_slection_in_hunk = - hunk.lines.iter().any(|line| { - let line: DiffLinePosition = line.into(); - lines.contains(&line) - }); + if !first_hunk_encountered { + let any_slection_in_hunk = + hunk.lines.iter().any(|line| { + let line: DiffLinePosition = line.into(); + lines.contains(&line) + }); - first_hunk_encountered = any_slection_in_hunk; - } + first_hunk_encountered = any_slection_in_hunk; + } - if first_hunk_encountered { - new_content.catchup_to_hunkstart(hunk_start, old_lines); + if first_hunk_encountered { + new_content.catchup_to_hunkstart(hunk_start, old_lines); - for hunk_line in &hunk.lines { - let hunk_line_pos: DiffLinePosition = - hunk_line.into(); - let selected_line = lines.contains(&hunk_line_pos); + for hunk_line in &hunk.lines { + let hunk_line_pos: DiffLinePosition = + hunk_line.into(); + let selected_line = lines.contains(&hunk_line_pos); - log::debug!( - // println!( - "{} line: {} [{:?} old, {:?} new] -> {}", - if selected_line { "*" } else { " " }, - hunk_line.origin(), - hunk_line.old_lineno(), - hunk_line.new_lineno(), - String::from_utf8_lossy(hunk_line.content()) - .trim() - ); + log::debug!( + // println!( + "{} line: {} [{:?} old, {:?} new] -> {}", + if selected_line { "*" } else { " " }, + hunk_line.origin(), + hunk_line.old_lineno(), + hunk_line.new_lineno(), + String::from_utf8_lossy(hunk_line.content()) + .trim() + ); - if hunk_line.origin_value() - == DiffLineType::DeleteEOFNL - || hunk_line.origin_value() - == DiffLineType::AddEOFNL - { - break; - } + if hunk_line.origin_value() + == DiffLineType::DeleteEOFNL + || hunk_line.origin_value() + == DiffLineType::AddEOFNL + { + break; + } - if (is_staged && !selected_line) - || (!is_staged && selected_line) - { - if hunk_line.origin_value() == added { - new_content.add_from_hunk(hunk_line)?; - if is_staged { - new_content.skip_old_line(); - } - } else if hunk_line.origin_value() == deleted { - if !is_staged { - new_content.skip_old_line(); - } - } else { - new_content.add_old_line(old_lines); - } - } else { - if hunk_line.origin_value() != added { - new_content.add_from_hunk(hunk_line)?; - } + if (is_staged && !selected_line) + || (!is_staged && selected_line) + { + if hunk_line.origin_value() == added { + new_content.add_from_hunk(hunk_line)?; + if is_staged { + new_content.skip_old_line(); + } + } else if hunk_line.origin_value() == deleted { + if !is_staged { + new_content.skip_old_line(); + } + } else { + new_content.add_old_line(old_lines); + } + } else { + if hunk_line.origin_value() != added { + new_content.add_from_hunk(hunk_line)?; + } - if (is_staged - && hunk_line.origin_value() != deleted) - || (!is_staged - && hunk_line.origin_value() != added) - { - new_content.skip_old_line(); - } - } - } - } - } + if (is_staged + && hunk_line.origin_value() != deleted) + || (!is_staged + && hunk_line.origin_value() != added) + { + new_content.skip_old_line(); + } + } + } + } + } - Ok(new_content.finish(old_lines)) + Ok(new_content.finish(old_lines)) } pub fn load_file( - repo: &Repository, - file_path: &str, + repo: &Repository, + file_path: &str, ) -> Result { - let repo_path = work_dir(repo)?; - let mut file = File::open(repo_path.join(file_path).as_path())?; - let mut res = String::new(); - file.read_to_string(&mut res)?; + let repo_path = work_dir(repo)?; + let mut file = File::open(repo_path.join(file_path).as_path())?; + let mut res = String::new(); + file.read_to_string(&mut res)?; - Ok(res) + Ok(res) } diff --git a/asyncgit/src/sync/staging/stage_tracked.rs b/asyncgit/src/sync/staging/stage_tracked.rs index a05647e7..1a9f7f2b 100644 --- a/asyncgit/src/sync/staging/stage_tracked.rs +++ b/asyncgit/src/sync/staging/stage_tracked.rs @@ -1,10 +1,10 @@ use super::apply_selection; use crate::{ - error::{Error, Result}, - sync::{ - diff::DiffLinePosition, - patches::get_file_diff_patch_and_hunklines, utils::repo, - }, + error::{Error, Result}, + sync::{ + diff::DiffLinePosition, + patches::get_file_diff_patch_and_hunklines, utils::repo, + }, }; use easy_cast::Conv; use scopetime::scope_time; @@ -12,185 +12,185 @@ use std::path::Path; /// pub fn stage_lines( - repo_path: &str, - file_path: &str, - is_stage: bool, - lines: &[DiffLinePosition], + repo_path: &str, + file_path: &str, + is_stage: bool, + lines: &[DiffLinePosition], ) -> Result<()> { - scope_time!("stage_lines"); + scope_time!("stage_lines"); - if lines.is_empty() { - return Ok(()); - } + if lines.is_empty() { + return Ok(()); + } - let repo = repo(repo_path)?; - // log::debug!("stage_lines: {:?}", lines); + let repo = repo(repo_path)?; + // log::debug!("stage_lines: {:?}", lines); - let mut index = repo.index()?; - index.read(true)?; - let mut idx = - index.get_path(Path::new(file_path), 0).ok_or_else(|| { - Error::Generic(String::from( - "only non new files supported", - )) - })?; - let blob = repo.find_blob(idx.id)?; - let indexed_content = String::from_utf8(blob.content().into())?; + let mut index = repo.index()?; + index.read(true)?; + let mut idx = + index.get_path(Path::new(file_path), 0).ok_or_else(|| { + Error::Generic(String::from( + "only non new files supported", + )) + })?; + let blob = repo.find_blob(idx.id)?; + let indexed_content = String::from_utf8(blob.content().into())?; - let new_content = { - let (_patch, hunks) = get_file_diff_patch_and_hunklines( - &repo, file_path, is_stage, false, - )?; + let new_content = { + let (_patch, hunks) = get_file_diff_patch_and_hunklines( + &repo, file_path, is_stage, false, + )?; - let old_lines = indexed_content.lines().collect::>(); + let old_lines = indexed_content.lines().collect::>(); - apply_selection(lines, &hunks, &old_lines, is_stage, false)? - }; + apply_selection(lines, &hunks, &old_lines, is_stage, false)? + }; - let blob_id = repo.blob(new_content.as_bytes())?; + let blob_id = repo.blob(new_content.as_bytes())?; - idx.id = blob_id; - idx.file_size = u32::try_conv(new_content.as_bytes().len())?; - index.add(&idx)?; + idx.id = blob_id; + idx.file_size = u32::try_conv(new_content.as_bytes().len())?; + index.add(&idx)?; - index.write()?; - index.read(true)?; + index.write()?; + index.read(true)?; - Ok(()) + Ok(()) } #[cfg(test)] mod test { - use super::*; - use crate::sync::{ - diff::get_diff, - tests::{get_statuses, repo_init, write_commit_file}, - utils::{repo_write_file, stage_add_file}, - }; + use super::*; + use crate::sync::{ + diff::get_diff, + tests::{get_statuses, repo_init, write_commit_file}, + utils::{repo_write_file, stage_add_file}, + }; - #[test] - fn test_stage() { - static FILE_1: &str = r"0 + #[test] + fn test_stage() { + static FILE_1: &str = r"0 "; - static FILE_2: &str = r"0 + static FILE_2: &str = r"0 1 2 3 "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - stage_lines( - path, - "test.txt", - false, - &[DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }], - ) - .unwrap(); + stage_lines( + path, + "test.txt", + false, + &[DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }], + ) + .unwrap(); - let diff = get_diff(path, "test.txt", true).unwrap(); + let diff = get_diff(path, "test.txt", true).unwrap(); - assert_eq!(diff.lines, 3); - assert_eq!( - diff.hunks[0].lines[0].content, - String::from("@@ -1 +1,2 @@\n") - ); - } + assert_eq!(diff.lines, 3); + assert_eq!( + diff.hunks[0].lines[0].content, + String::from("@@ -1 +1,2 @@\n") + ); + } - #[test] - fn test_panic_stage_no_newline() { - static FILE_1: &str = r"a = 1 + #[test] + fn test_panic_stage_no_newline() { + static FILE_1: &str = r"a = 1 b = 2"; - static FILE_2: &str = r"a = 2 + static FILE_2: &str = r"a = 2 b = 3 c = 4"; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - stage_lines( - path, - "test.txt", - false, - &[ - DiffLinePosition { - old_lineno: Some(1), - new_lineno: None, - }, - DiffLinePosition { - old_lineno: Some(2), - new_lineno: None, - }, - ], - ) - .unwrap(); + stage_lines( + path, + "test.txt", + false, + &[ + DiffLinePosition { + old_lineno: Some(1), + new_lineno: None, + }, + DiffLinePosition { + old_lineno: Some(2), + new_lineno: None, + }, + ], + ) + .unwrap(); - let diff = get_diff(path, "test.txt", true).unwrap(); + let diff = get_diff(path, "test.txt", true).unwrap(); - assert_eq!(diff.lines, 5); - assert_eq!( - diff.hunks[0].lines[0].content, - String::from("@@ -1,2 +1 @@\n") - ); - } + assert_eq!(diff.lines, 5); + assert_eq!( + diff.hunks[0].lines[0].content, + String::from("@@ -1,2 +1 @@\n") + ); + } - #[test] - fn test_unstage() { - static FILE_1: &str = r"0 + #[test] + fn test_unstage() { + static FILE_1: &str = r"0 "; - static FILE_2: &str = r"0 + static FILE_2: &str = r"0 1 2 3 "; - let (path, repo) = repo_init().unwrap(); - let path = path.path().to_str().unwrap(); + let (path, repo) = repo_init().unwrap(); + let path = path.path().to_str().unwrap(); - write_commit_file(&repo, "test.txt", FILE_1, "c1"); + write_commit_file(&repo, "test.txt", FILE_1, "c1"); - repo_write_file(&repo, "test.txt", FILE_2).unwrap(); + repo_write_file(&repo, "test.txt", FILE_2).unwrap(); - assert_eq!(get_statuses(path), (1, 0)); + assert_eq!(get_statuses(path), (1, 0)); - stage_add_file(path, &Path::new("test.txt")).unwrap(); + stage_add_file(path, &Path::new("test.txt")).unwrap(); - assert_eq!(get_statuses(path), (0, 1)); + assert_eq!(get_statuses(path), (0, 1)); - let diff_before = get_diff(path, "test.txt", true).unwrap(); + let diff_before = get_diff(path, "test.txt", true).unwrap(); - assert_eq!(diff_before.lines, 5); + assert_eq!(diff_before.lines, 5); - stage_lines( - path, - "test.txt", - true, - &[DiffLinePosition { - old_lineno: None, - new_lineno: Some(2), - }], - ) - .unwrap(); + stage_lines( + path, + "test.txt", + true, + &[DiffLinePosition { + old_lineno: None, + new_lineno: Some(2), + }], + ) + .unwrap(); - assert_eq!(get_statuses(path), (1, 1)); + assert_eq!(get_statuses(path), (1, 1)); - let diff = get_diff(path, "test.txt", true).unwrap(); + let diff = get_diff(path, "test.txt", true).unwrap(); - assert_eq!(diff.lines, 4); - } + assert_eq!(diff.lines, 4); + } } diff --git a/asyncgit/src/sync/stash.rs b/asyncgit/src/sync/stash.rs index e19079d9..f98679a2 100644 --- a/asyncgit/src/sync/stash.rs +++ b/asyncgit/src/sync/stash.rs @@ -1,370 +1,370 @@ use super::{utils::repo, CommitId}; use crate::error::{Error, Result}; use git2::{ - build::CheckoutBuilder, Oid, Repository, StashApplyOptions, - StashFlags, + build::CheckoutBuilder, Oid, Repository, StashApplyOptions, + StashFlags, }; use scopetime::scope_time; /// pub fn get_stashes(repo_path: &str) -> Result> { - scope_time!("get_stashes"); + scope_time!("get_stashes"); - let mut repo = repo(repo_path)?; + let mut repo = repo(repo_path)?; - let mut list = Vec::new(); + let mut list = Vec::new(); - repo.stash_foreach(|_index, _msg, id| { - list.push((*id).into()); - true - })?; + repo.stash_foreach(|_index, _msg, id| { + list.push((*id).into()); + true + })?; - Ok(list) + Ok(list) } /// checks whether a given commit is a stash commit. pub fn is_stash_commit( - repo_path: &str, - id: &CommitId, + repo_path: &str, + id: &CommitId, ) -> Result { - let stashes = get_stashes(repo_path)?; - Ok(stashes.contains(id)) + let stashes = get_stashes(repo_path)?; + Ok(stashes.contains(id)) } /// pub fn stash_drop(repo_path: &str, stash_id: CommitId) -> Result<()> { - scope_time!("stash_drop"); + scope_time!("stash_drop"); - let mut repo = repo(repo_path)?; + let mut repo = repo(repo_path)?; - let index = get_stash_index(&mut repo, stash_id.into())?; + let index = get_stash_index(&mut repo, stash_id.into())?; - repo.stash_drop(index)?; + repo.stash_drop(index)?; - Ok(()) + Ok(()) } /// pub fn stash_pop(repo_path: &str, stash_id: CommitId) -> Result<()> { - scope_time!("stash_pop"); + scope_time!("stash_pop"); - let mut repo = repo(repo_path)?; + let mut repo = repo(repo_path)?; - let index = get_stash_index(&mut repo, stash_id.into())?; + let index = get_stash_index(&mut repo, stash_id.into())?; - repo.stash_pop(index, None)?; + repo.stash_pop(index, None)?; - Ok(()) + Ok(()) } /// pub fn stash_apply( - repo_path: &str, - stash_id: CommitId, - allow_conflicts: bool, + repo_path: &str, + stash_id: CommitId, + allow_conflicts: bool, ) -> Result<()> { - scope_time!("stash_apply"); + scope_time!("stash_apply"); - let mut repo = repo(repo_path)?; + let mut repo = repo(repo_path)?; - let index = get_stash_index(&mut repo, stash_id.get_oid())?; + let index = get_stash_index(&mut repo, stash_id.get_oid())?; - let mut checkout = CheckoutBuilder::new(); - checkout.allow_conflicts(allow_conflicts); + let mut checkout = CheckoutBuilder::new(); + checkout.allow_conflicts(allow_conflicts); - let mut opt = StashApplyOptions::default(); - opt.checkout_options(checkout); - repo.stash_apply(index, Some(&mut opt))?; + let mut opt = StashApplyOptions::default(); + opt.checkout_options(checkout); + repo.stash_apply(index, Some(&mut opt))?; - Ok(()) + Ok(()) } fn get_stash_index( - repo: &mut Repository, - stash_id: Oid, + repo: &mut Repository, + stash_id: Oid, ) -> Result { - let mut idx = None; + let mut idx = None; - repo.stash_foreach(|index, _msg, id| { - if *id == stash_id { - idx = Some(index); - false - } else { - true - } - })?; + repo.stash_foreach(|index, _msg, id| { + if *id == stash_id { + idx = Some(index); + false + } else { + true + } + })?; - idx.ok_or_else(|| { - Error::Generic("stash commit not found".to_string()) - }) + idx.ok_or_else(|| { + Error::Generic("stash commit not found".to_string()) + }) } /// pub fn stash_save( - repo_path: &str, - message: Option<&str>, - include_untracked: bool, - keep_index: bool, + repo_path: &str, + message: Option<&str>, + include_untracked: bool, + keep_index: bool, ) -> Result { - scope_time!("stash_save"); + scope_time!("stash_save"); - let mut repo = repo(repo_path)?; + let mut repo = repo(repo_path)?; - let sig = repo.signature()?; + let sig = repo.signature()?; - let mut options = StashFlags::DEFAULT; + let mut options = StashFlags::DEFAULT; - if include_untracked { - options.insert(StashFlags::INCLUDE_UNTRACKED); - } - if keep_index { - options.insert(StashFlags::KEEP_INDEX); - } + if include_untracked { + options.insert(StashFlags::INCLUDE_UNTRACKED); + } + if keep_index { + options.insert(StashFlags::KEEP_INDEX); + } - let id = repo.stash_save2(&sig, message, Some(options))?; + let id = repo.stash_save2(&sig, message, Some(options))?; - Ok(CommitId::new(id)) + Ok(CommitId::new(id)) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::{ - commit, get_commit_files, get_commits_info, stage_add_file, - tests::{ - debug_cmd_print, get_statuses, repo_init, - write_commit_file, - }, - utils::{repo_read_file, repo_write_file}, - }; - use std::{fs::File, io::Write, path::Path}; + use super::*; + use crate::sync::{ + commit, get_commit_files, get_commits_info, stage_add_file, + tests::{ + debug_cmd_print, get_statuses, repo_init, + write_commit_file, + }, + utils::{repo_read_file, repo_write_file}, + }; + use std::{fs::File, io::Write, path::Path}; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!( - stash_save(repo_path, None, true, false).is_ok(), - false - ); + assert_eq!( + stash_save(repo_path, None, true, false).is_ok(), + false + ); - assert_eq!(get_stashes(repo_path).unwrap().is_empty(), true); - } + assert_eq!(get_stashes(repo_path).unwrap().is_empty(), true); + } - #[test] - fn test_stashing() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stashing() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join("foo.txt"))? - .write_all(b"test\nfoo")?; + File::create(&root.join("foo.txt"))? + .write_all(b"test\nfoo")?; - assert_eq!(get_statuses(repo_path), (1, 0)); + assert_eq!(get_statuses(repo_path), (1, 0)); - stash_save(repo_path, None, true, false)?; + stash_save(repo_path, None, true, false)?; - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - Ok(()) - } + Ok(()) + } - #[test] - fn test_stashes() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stashes() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join("foo.txt"))? - .write_all(b"test\nfoo")?; + File::create(&root.join("foo.txt"))? + .write_all(b"test\nfoo")?; - stash_save(repo_path, Some("foo"), true, false)?; + stash_save(repo_path, Some("foo"), true, false)?; - let res = get_stashes(repo_path)?; + let res = get_stashes(repo_path)?; - assert_eq!(res.len(), 1); + assert_eq!(res.len(), 1); - let infos = - get_commits_info(repo_path, &[res[0]], 100).unwrap(); + let infos = + get_commits_info(repo_path, &[res[0]], 100).unwrap(); - assert_eq!(infos[0].message, "On master: foo"); + assert_eq!(infos[0].message, "On master: foo"); - Ok(()) - } + Ok(()) + } - #[test] - fn test_stash_nothing_untracked() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_nothing_untracked() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join("foo.txt"))? - .write_all(b"test\nfoo")?; + File::create(&root.join("foo.txt"))? + .write_all(b"test\nfoo")?; - assert!( - stash_save(repo_path, Some("foo"), false, false).is_err() - ); + assert!( + stash_save(repo_path, Some("foo"), false, false).is_err() + ); - Ok(()) - } + Ok(()) + } - #[test] - fn test_stash_without_2nd_parent() -> Result<()> { - let file_path1 = Path::new("file1.txt"); - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_without_2nd_parent() -> Result<()> { + let file_path1 = Path::new("file1.txt"); + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path1))?.write_all(b"test")?; - stage_add_file(repo_path, file_path1)?; - commit(repo_path, "c1")?; + File::create(&root.join(file_path1))?.write_all(b"test")?; + stage_add_file(repo_path, file_path1)?; + commit(repo_path, "c1")?; - File::create(&root.join(file_path1))? - .write_all(b"modified")?; + File::create(&root.join(file_path1))? + .write_all(b"modified")?; - //NOTE: apparently `libgit2` works differently to git stash in - //always creating the third parent for untracked files while the - //cli skips that step when no new files exist - debug_cmd_print(repo_path, "git stash"); + //NOTE: apparently `libgit2` works differently to git stash in + //always creating the third parent for untracked files while the + //cli skips that step when no new files exist + debug_cmd_print(repo_path, "git stash"); - let stash = get_stashes(repo_path)?[0]; + let stash = get_stashes(repo_path)?[0]; - let diff = get_commit_files(repo_path, stash)?; + let diff = get_commit_files(repo_path, stash)?; - assert_eq!(diff.len(), 1); + assert_eq!(diff.len(), 1); - Ok(()) - } + Ok(()) + } - #[test] - fn test_stash_apply_conflict() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_apply_conflict() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - repo_write_file(&repo, "test.txt", "test").unwrap(); + repo_write_file(&repo, "test.txt", "test").unwrap(); - let id = - stash_save(repo_path, Some("foo"), true, false).unwrap(); + let id = + stash_save(repo_path, Some("foo"), true, false).unwrap(); - repo_write_file(&repo, "test.txt", "foo").unwrap(); + repo_write_file(&repo, "test.txt", "foo").unwrap(); - let res = stash_apply(repo_path, id, false); + let res = stash_apply(repo_path, id, false); - assert!(res.is_err()); - } + assert!(res.is_err()); + } - #[test] - fn test_stash_apply_conflict2() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_apply_conflict2() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - write_commit_file(&repo, "test.txt", "test", "c1"); + write_commit_file(&repo, "test.txt", "test", "c1"); - repo_write_file(&repo, "test.txt", "test2").unwrap(); + repo_write_file(&repo, "test.txt", "test2").unwrap(); - let id = - stash_save(repo_path, Some("foo"), true, false).unwrap(); + let id = + stash_save(repo_path, Some("foo"), true, false).unwrap(); - repo_write_file(&repo, "test.txt", "test3").unwrap(); + repo_write_file(&repo, "test.txt", "test3").unwrap(); - let res = stash_apply(repo_path, id, false); + let res = stash_apply(repo_path, id, false); - assert!(res.is_err()); - } + assert!(res.is_err()); + } - #[test] - fn test_stash_apply_creating_conflict() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_apply_creating_conflict() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - write_commit_file(&repo, "test.txt", "test", "c1"); + write_commit_file(&repo, "test.txt", "test", "c1"); - repo_write_file(&repo, "test.txt", "test2").unwrap(); + repo_write_file(&repo, "test.txt", "test2").unwrap(); - let id = - stash_save(repo_path, Some("foo"), true, false).unwrap(); + let id = + stash_save(repo_path, Some("foo"), true, false).unwrap(); - repo_write_file(&repo, "test.txt", "test3").unwrap(); + repo_write_file(&repo, "test.txt", "test3").unwrap(); - let res = stash_apply(repo_path, id, false); + let res = stash_apply(repo_path, id, false); - assert!(res.is_err()); + assert!(res.is_err()); - let res = stash_apply(repo_path, id, true); + let res = stash_apply(repo_path, id, true); - assert!(res.is_ok()); - } + assert!(res.is_ok()); + } - #[test] - fn test_stash_pop_no_conflict() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_pop_no_conflict() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - write_commit_file(&repo, "test.txt", "test", "c1"); + write_commit_file(&repo, "test.txt", "test", "c1"); - repo_write_file(&repo, "test.txt", "test2").unwrap(); + repo_write_file(&repo, "test.txt", "test2").unwrap(); - let id = - stash_save(repo_path, Some("foo"), true, false).unwrap(); + let id = + stash_save(repo_path, Some("foo"), true, false).unwrap(); - let res = stash_pop(repo_path, id); + let res = stash_pop(repo_path, id); - assert!(res.is_ok()); - assert_eq!( - repo_read_file(&repo, "test.txt").unwrap(), - "test2" - ); - } + assert!(res.is_ok()); + assert_eq!( + repo_read_file(&repo, "test.txt").unwrap(), + "test2" + ); + } - #[test] - fn test_stash_pop_conflict() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_pop_conflict() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - repo_write_file(&repo, "test.txt", "test").unwrap(); + repo_write_file(&repo, "test.txt", "test").unwrap(); - let id = - stash_save(repo_path, Some("foo"), true, false).unwrap(); + let id = + stash_save(repo_path, Some("foo"), true, false).unwrap(); - repo_write_file(&repo, "test.txt", "test2").unwrap(); + repo_write_file(&repo, "test.txt", "test2").unwrap(); - let res = stash_pop(repo_path, id); + let res = stash_pop(repo_path, id); - assert!(res.is_err()); - assert_eq!( - repo_read_file(&repo, "test.txt").unwrap(), - "test2" - ); - } + assert!(res.is_err()); + assert_eq!( + repo_read_file(&repo, "test.txt").unwrap(), + "test2" + ); + } - #[test] - fn test_stash_pop_conflict_after_commit() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stash_pop_conflict_after_commit() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - write_commit_file(&repo, "test.txt", "test", "c1"); + write_commit_file(&repo, "test.txt", "test", "c1"); - repo_write_file(&repo, "test.txt", "test2").unwrap(); + repo_write_file(&repo, "test.txt", "test2").unwrap(); - let id = - stash_save(repo_path, Some("foo"), true, false).unwrap(); + let id = + stash_save(repo_path, Some("foo"), true, false).unwrap(); - repo_write_file(&repo, "test.txt", "test3").unwrap(); + repo_write_file(&repo, "test.txt", "test3").unwrap(); - let res = stash_pop(repo_path, id); + let res = stash_pop(repo_path, id); - assert!(res.is_err()); - assert_eq!( - repo_read_file(&repo, "test.txt").unwrap(), - "test3" - ); - } + assert!(res.is_err()); + assert_eq!( + repo_read_file(&repo, "test.txt").unwrap(), + "test3" + ); + } } diff --git a/asyncgit/src/sync/state.rs b/asyncgit/src/sync/state.rs index 947528f2..410f6dfa 100644 --- a/asyncgit/src/sync/state.rs +++ b/asyncgit/src/sync/state.rs @@ -5,29 +5,29 @@ use scopetime::scope_time; /// #[derive(Debug, PartialEq)] pub enum RepoState { - /// - Clean, - /// - Merge, - /// - Other, + /// + Clean, + /// + Merge, + /// + Other, } impl From for RepoState { - fn from(state: RepositoryState) -> Self { - match state { - RepositoryState::Clean => Self::Clean, - RepositoryState::Merge => Self::Merge, - _ => Self::Other, - } - } + fn from(state: RepositoryState) -> Self { + match state { + RepositoryState::Clean => Self::Clean, + RepositoryState::Merge => Self::Merge, + _ => Self::Other, + } + } } /// pub fn repo_state(repo_path: &str) -> Result { - scope_time!("repo_state"); + scope_time!("repo_state"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - Ok(repo.state().into()) + Ok(repo.state().into()) } diff --git a/asyncgit/src/sync/status.rs b/asyncgit/src/sync/status.rs index 077c8e00..fcecf5b6 100644 --- a/asyncgit/src/sync/status.rs +++ b/asyncgit/src/sync/status.rs @@ -1,9 +1,9 @@ //! sync git api for fetching a status use crate::{ - error::Error, - error::Result, - sync::{config::untracked_files_config_repo, utils}, + error::Error, + error::Result, + sync::{config::untracked_files_config_repo, utils}, }; use git2::{Delta, Status, StatusOptions, StatusShow}; use scopetime::scope_time; @@ -12,143 +12,143 @@ use std::path::Path; /// #[derive(Copy, Clone, Hash, PartialEq, Debug)] pub enum StatusItemType { - /// - New, - /// - Modified, - /// - Deleted, - /// - Renamed, - /// - Typechange, - /// - Conflicted, + /// + New, + /// + Modified, + /// + Deleted, + /// + Renamed, + /// + Typechange, + /// + Conflicted, } impl From for StatusItemType { - fn from(s: Status) -> Self { - if s.is_index_new() || s.is_wt_new() { - Self::New - } else if s.is_index_deleted() || s.is_wt_deleted() { - Self::Deleted - } else if s.is_index_renamed() || s.is_wt_renamed() { - Self::Renamed - } else if s.is_index_typechange() || s.is_wt_typechange() { - Self::Typechange - } else if s.is_conflicted() { - Self::Conflicted - } else { - Self::Modified - } - } + fn from(s: Status) -> Self { + if s.is_index_new() || s.is_wt_new() { + Self::New + } else if s.is_index_deleted() || s.is_wt_deleted() { + Self::Deleted + } else if s.is_index_renamed() || s.is_wt_renamed() { + Self::Renamed + } else if s.is_index_typechange() || s.is_wt_typechange() { + Self::Typechange + } else if s.is_conflicted() { + Self::Conflicted + } else { + Self::Modified + } + } } impl From for StatusItemType { - fn from(d: Delta) -> Self { - match d { - Delta::Added => Self::New, - Delta::Deleted => Self::Deleted, - Delta::Renamed => Self::Renamed, - Delta::Typechange => Self::Typechange, - _ => Self::Modified, - } - } + fn from(d: Delta) -> Self { + match d { + Delta::Added => Self::New, + Delta::Deleted => Self::Deleted, + Delta::Renamed => Self::Renamed, + Delta::Typechange => Self::Typechange, + _ => Self::Modified, + } + } } /// #[derive(Clone, Hash, PartialEq, Debug)] pub struct StatusItem { - /// - pub path: String, - /// - pub status: StatusItemType, + /// + pub path: String, + /// + pub status: StatusItemType, } /// #[derive(Copy, Clone, Hash, PartialEq, Debug)] pub enum StatusType { - /// - WorkingDir, - /// - Stage, - /// - Both, + /// + WorkingDir, + /// + Stage, + /// + Both, } impl Default for StatusType { - fn default() -> Self { - Self::WorkingDir - } + fn default() -> Self { + Self::WorkingDir + } } impl From for StatusShow { - fn from(s: StatusType) -> Self { - match s { - StatusType::WorkingDir => Self::Workdir, - StatusType::Stage => Self::Index, - StatusType::Both => Self::IndexAndWorkdir, - } - } + fn from(s: StatusType) -> Self { + match s { + StatusType::WorkingDir => Self::Workdir, + StatusType::Stage => Self::Index, + StatusType::Both => Self::IndexAndWorkdir, + } + } } /// gurantees sorting pub fn get_status( - repo_path: &str, - status_type: StatusType, + repo_path: &str, + status_type: StatusType, ) -> Result> { - scope_time!("get_status"); + scope_time!("get_status"); - let repo = utils::repo(repo_path)?; + let repo = utils::repo(repo_path)?; - let show_untracked = untracked_files_config_repo(&repo)?; + let show_untracked = untracked_files_config_repo(&repo)?; - let mut options = StatusOptions::default(); - options - .show(status_type.into()) - .update_index(true) - .include_untracked(show_untracked.include_untracked()) - .renames_head_to_index(true) - .recurse_untracked_dirs( - show_untracked.recurse_untracked_dirs(), - ); + let mut options = StatusOptions::default(); + options + .show(status_type.into()) + .update_index(true) + .include_untracked(show_untracked.include_untracked()) + .renames_head_to_index(true) + .recurse_untracked_dirs( + show_untracked.recurse_untracked_dirs(), + ); - let statuses = repo.statuses(Some(&mut options))?; + let statuses = repo.statuses(Some(&mut options))?; - let mut res = Vec::with_capacity(statuses.len()); + let mut res = Vec::with_capacity(statuses.len()); - for e in statuses.iter() { - let status: Status = e.status(); + for e in statuses.iter() { + let status: Status = e.status(); - let path = match e.head_to_index() { - Some(diff) => diff - .new_file() - .path() - .and_then(Path::to_str) - .map(String::from) - .ok_or_else(|| { - Error::Generic( - "failed to get path to diff's new file." - .to_string(), - ) - })?, - None => e.path().map(String::from).ok_or_else(|| { - Error::Generic( - "failed to get the path to indexed file." - .to_string(), - ) - })?, - }; + let path = match e.head_to_index() { + Some(diff) => diff + .new_file() + .path() + .and_then(Path::to_str) + .map(String::from) + .ok_or_else(|| { + Error::Generic( + "failed to get path to diff's new file." + .to_string(), + ) + })?, + None => e.path().map(String::from).ok_or_else(|| { + Error::Generic( + "failed to get the path to indexed file." + .to_string(), + ) + })?, + }; - res.push(StatusItem { - path, - status: StatusItemType::from(status), - }); - } + res.push(StatusItem { + path, + status: StatusItemType::from(status), + }); + } - res.sort_by(|a, b| { - Path::new(a.path.as_str()).cmp(Path::new(b.path.as_str())) - }); + res.sort_by(|a, b| { + Path::new(a.path.as_str()).cmp(Path::new(b.path.as_str())) + }); - Ok(res) + Ok(res) } diff --git a/asyncgit/src/sync/tags.rs b/asyncgit/src/sync/tags.rs index 0c664660..98034fab 100644 --- a/asyncgit/src/sync/tags.rs +++ b/asyncgit/src/sync/tags.rs @@ -10,181 +10,181 @@ pub type Tags = BTreeMap; /// pub struct TagWithMetadata { - /// - pub name: String, - /// - pub author: String, - /// - pub time: i64, - /// - pub message: String, - /// - pub commit_id: CommitId, + /// + pub name: String, + /// + pub author: String, + /// + pub time: i64, + /// + pub message: String, + /// + pub commit_id: CommitId, } static MAX_MESSAGE_WIDTH: usize = 100; /// returns `Tags` type filled with all tags found in repo pub fn get_tags(repo_path: &str) -> Result { - scope_time!("get_tags"); + scope_time!("get_tags"); - let mut res = Tags::new(); - let mut adder = |key, value: String| { - if let Some(key) = res.get_mut(&key) { - key.push(value); - } else { - res.insert(key, vec![value]); - } - }; + let mut res = Tags::new(); + let mut adder = |key, value: String| { + if let Some(key) = res.get_mut(&key) { + key.push(value); + } else { + res.insert(key, vec![value]); + } + }; - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - repo.tag_foreach(|id, name| { - if let Ok(name) = - // skip the `refs/tags/` part - String::from_utf8(name[10..name.len()].into()) - { - //NOTE: find_tag (git_tag_lookup) only works on annotated tags - // lightweight tags `id` already points to the target commit - // see https://github.com/libgit2/libgit2/issues/5586 - if let Ok(commit) = repo - .find_tag(id) - .and_then(|tag| tag.target()) - .and_then(|target| target.peel_to_commit()) - { - adder(CommitId::new(commit.id()), name); - } else if repo.find_commit(id).is_ok() { - adder(CommitId::new(id), name); - } + repo.tag_foreach(|id, name| { + if let Ok(name) = + // skip the `refs/tags/` part + String::from_utf8(name[10..name.len()].into()) + { + //NOTE: find_tag (git_tag_lookup) only works on annotated tags + // lightweight tags `id` already points to the target commit + // see https://github.com/libgit2/libgit2/issues/5586 + if let Ok(commit) = repo + .find_tag(id) + .and_then(|tag| tag.target()) + .and_then(|target| target.peel_to_commit()) + { + adder(CommitId::new(commit.id()), name); + } else if repo.find_commit(id).is_ok() { + adder(CommitId::new(id), name); + } - return true; - } - false - })?; + return true; + } + false + })?; - Ok(res) + Ok(res) } /// pub fn get_tags_with_metadata( - repo_path: &str, + repo_path: &str, ) -> Result> { - scope_time!("get_tags_with_metadata"); + scope_time!("get_tags_with_metadata"); - let tags_grouped_by_commit_id = get_tags(repo_path)?; + let tags_grouped_by_commit_id = get_tags(repo_path)?; - let tags_with_commit_id: Vec<(&str, &CommitId)> = - tags_grouped_by_commit_id - .iter() - .flat_map(|(commit_id, tags)| { - tags.iter() - .map(|tag| (tag.as_ref(), commit_id)) - .collect::>() - }) - .collect(); + let tags_with_commit_id: Vec<(&str, &CommitId)> = + tags_grouped_by_commit_id + .iter() + .flat_map(|(commit_id, tags)| { + tags.iter() + .map(|tag| (tag.as_ref(), commit_id)) + .collect::>() + }) + .collect(); - let unique_commit_ids: HashSet<_> = tags_with_commit_id - .iter() - .copied() - .map(|(_, &commit_id)| commit_id) - .collect(); - let mut commit_ids = Vec::with_capacity(unique_commit_ids.len()); - commit_ids.extend(unique_commit_ids); + let unique_commit_ids: HashSet<_> = tags_with_commit_id + .iter() + .copied() + .map(|(_, &commit_id)| commit_id) + .collect(); + let mut commit_ids = Vec::with_capacity(unique_commit_ids.len()); + commit_ids.extend(unique_commit_ids); - let commit_infos = - get_commits_info(repo_path, &commit_ids, MAX_MESSAGE_WIDTH)?; - let unique_commit_infos: HashMap<_, _> = commit_infos - .iter() - .map(|commit_info| (commit_info.id, commit_info)) - .collect(); + let commit_infos = + get_commits_info(repo_path, &commit_ids, MAX_MESSAGE_WIDTH)?; + let unique_commit_infos: HashMap<_, _> = commit_infos + .iter() + .map(|commit_info| (commit_info.id, commit_info)) + .collect(); - let mut tags: Vec = tags_with_commit_id - .into_iter() - .filter_map(|(tag, commit_id)| { - unique_commit_infos.get(commit_id).map(|commit_info| { - TagWithMetadata { - name: String::from(tag), - author: commit_info.author.clone(), - time: commit_info.time, - message: commit_info.message.clone(), - commit_id: *commit_id, - } - }) - }) - .collect(); + let mut tags: Vec = tags_with_commit_id + .into_iter() + .filter_map(|(tag, commit_id)| { + unique_commit_infos.get(commit_id).map(|commit_info| { + TagWithMetadata { + name: String::from(tag), + author: commit_info.author.clone(), + time: commit_info.time, + message: commit_info.message.clone(), + commit_id: *commit_id, + } + }) + }) + .collect(); - tags.sort_unstable_by(|a, b| b.time.cmp(&a.time)); + tags.sort_unstable_by(|a, b| b.time.cmp(&a.time)); - Ok(tags) + Ok(tags) } /// pub fn delete_tag(repo_path: &str, tag_name: &str) -> Result<()> { - scope_time!("delete_tag"); + scope_time!("delete_tag"); - let repo = repo(repo_path)?; - repo.tag_delete(tag_name)?; + let repo = repo(repo_path)?; + repo.tag_delete(tag_name)?; - Ok(()) + Ok(()) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::tests::repo_init; - use git2::ObjectType; + use super::*; + use crate::sync::tests::repo_init; + use git2::ObjectType; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_tags(repo_path).unwrap().is_empty(), true); - } + assert_eq!(get_tags(repo_path).unwrap().is_empty(), true); + } - #[test] - fn test_multitags() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_multitags() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let sig = repo.signature().unwrap(); - let head_id = repo.head().unwrap().target().unwrap(); - let target = repo - .find_object( - repo.head().unwrap().target().unwrap(), - Some(ObjectType::Commit), - ) - .unwrap(); + let sig = repo.signature().unwrap(); + let head_id = repo.head().unwrap().target().unwrap(); + let target = repo + .find_object( + repo.head().unwrap().target().unwrap(), + Some(ObjectType::Commit), + ) + .unwrap(); - repo.tag("a", &target, &sig, "", false).unwrap(); - repo.tag("b", &target, &sig, "", false).unwrap(); + repo.tag("a", &target, &sig, "", false).unwrap(); + repo.tag("b", &target, &sig, "", false).unwrap(); - assert_eq!( - get_tags(repo_path).unwrap()[&CommitId::new(head_id)], - vec!["a", "b"] - ); + assert_eq!( + get_tags(repo_path).unwrap()[&CommitId::new(head_id)], + vec!["a", "b"] + ); - let tags = get_tags_with_metadata(repo_path).unwrap(); + let tags = get_tags_with_metadata(repo_path).unwrap(); - assert_eq!(tags.len(), 2); - assert_eq!(tags[0].name, "a"); - assert_eq!(tags[0].message, "initial"); - assert_eq!(tags[1].name, "b"); - assert_eq!(tags[1].message, "initial"); - assert_eq!(tags[0].commit_id, tags[1].commit_id); + assert_eq!(tags.len(), 2); + assert_eq!(tags[0].name, "a"); + assert_eq!(tags[0].message, "initial"); + assert_eq!(tags[1].name, "b"); + assert_eq!(tags[1].message, "initial"); + assert_eq!(tags[0].commit_id, tags[1].commit_id); - delete_tag(repo_path, "a").unwrap(); + delete_tag(repo_path, "a").unwrap(); - let tags = get_tags(repo_path).unwrap(); + let tags = get_tags(repo_path).unwrap(); - assert_eq!(tags.len(), 1); + assert_eq!(tags.len(), 1); - delete_tag(repo_path, "b").unwrap(); + delete_tag(repo_path, "b").unwrap(); - let tags = get_tags(repo_path).unwrap(); + let tags = get_tags(repo_path).unwrap(); - assert_eq!(tags.len(), 0); - } + assert_eq!(tags.len(), 0); + } } diff --git a/asyncgit/src/sync/tree.rs b/asyncgit/src/sync/tree.rs index c975689a..5f4a4081 100644 --- a/asyncgit/src/sync/tree.rs +++ b/asyncgit/src/sync/tree.rs @@ -1,250 +1,250 @@ use super::CommitId; use crate::{ - error::{Error, Result}, - sync::utils::repo, + error::{Error, Result}, + sync::utils::repo, }; use git2::{Oid, Repository, Tree}; use scopetime::scope_time; use std::{ - cmp::Ordering, - path::{Path, PathBuf}, + cmp::Ordering, + path::{Path, PathBuf}, }; /// `tree_files` returns a list of `FileTree` #[derive(Debug, PartialEq, Clone)] pub struct TreeFile { - /// path of this file - pub path: PathBuf, - /// unix filemode - pub filemode: i32, - // internal object id - id: Oid, + /// path of this file + pub path: PathBuf, + /// unix filemode + pub filemode: i32, + // internal object id + id: Oid, } /// guarantees sorting the result pub fn tree_files( - repo_path: &str, - commit: CommitId, + repo_path: &str, + commit: CommitId, ) -> Result> { - scope_time!("tree_files"); + scope_time!("tree_files"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let commit = repo.find_commit(commit.into())?; - let tree = commit.tree()?; + let commit = repo.find_commit(commit.into())?; + let tree = commit.tree()?; - let mut files: Vec = Vec::new(); + let mut files: Vec = Vec::new(); - tree_recurse(&repo, &PathBuf::from("./"), &tree, &mut files)?; + tree_recurse(&repo, &PathBuf::from("./"), &tree, &mut files)?; - sort_file_list(&mut files); + sort_file_list(&mut files); - Ok(files) + Ok(files) } fn sort_file_list(files: &mut Vec) { - files.sort_by(|a, b| path_cmp(&a.path, &b.path)); + files.sort_by(|a, b| path_cmp(&a.path, &b.path)); } // applies topologically order on paths sorting fn path_cmp(a: &Path, b: &Path) -> Ordering { - let mut comp_a = a.components().into_iter().peekable(); - let mut comp_b = b.components().into_iter().peekable(); + let mut comp_a = a.components().into_iter().peekable(); + let mut comp_b = b.components().into_iter().peekable(); - loop { - let a = comp_a.next(); - let b = comp_b.next(); + loop { + let a = comp_a.next(); + let b = comp_b.next(); - let a_is_file = comp_a.peek().is_none(); - let b_is_file = comp_b.peek().is_none(); + let a_is_file = comp_a.peek().is_none(); + let b_is_file = comp_b.peek().is_none(); - if a_is_file && !b_is_file { - return Ordering::Greater; - } else if !a_is_file && b_is_file { - return Ordering::Less; - } + if a_is_file && !b_is_file { + return Ordering::Greater; + } else if !a_is_file && b_is_file { + return Ordering::Less; + } - let cmp = a.cmp(&b); - if cmp != Ordering::Equal { - return cmp; - } - } + let cmp = a.cmp(&b); + if cmp != Ordering::Equal { + return cmp; + } + } } /// will only work on utf8 content pub fn tree_file_content( - repo_path: &str, - file: &TreeFile, + repo_path: &str, + file: &TreeFile, ) -> Result { - scope_time!("tree_file_content"); + scope_time!("tree_file_content"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let blob = repo.find_blob(file.id)?; + let blob = repo.find_blob(file.id)?; - if blob.is_binary() { - return Err(Error::BinaryFile); - } + if blob.is_binary() { + return Err(Error::BinaryFile); + } - let content = String::from_utf8_lossy(blob.content()).to_string(); + let content = String::from_utf8_lossy(blob.content()).to_string(); - Ok(content) + Ok(content) } /// fn tree_recurse( - repo: &Repository, - path: &Path, - tree: &Tree, - out: &mut Vec, + repo: &Repository, + path: &Path, + tree: &Tree, + out: &mut Vec, ) -> Result<()> { - out.reserve(tree.len()); + out.reserve(tree.len()); - for e in tree { - let p = String::from_utf8_lossy(e.name_bytes()); - let path = path.join(p.to_string()); - match e.kind() { - Some(git2::ObjectType::Blob) => { - let id = e.id(); - let filemode = e.filemode(); - out.push(TreeFile { path, filemode, id }); - } - Some(git2::ObjectType::Tree) => { - let obj = e.to_object(repo)?; - let tree = obj.peel_to_tree()?; - tree_recurse(repo, &path, &tree, out)?; - } - Some(_) | None => (), - } - } - Ok(()) + for e in tree { + let p = String::from_utf8_lossy(e.name_bytes()); + let path = path.join(p.to_string()); + match e.kind() { + Some(git2::ObjectType::Blob) => { + let id = e.id(); + let filemode = e.filemode(); + out.push(TreeFile { path, filemode, id }); + } + Some(git2::ObjectType::Tree) => { + let obj = e.to_object(repo)?; + let tree = obj.peel_to_tree()?; + tree_recurse(repo, &path, &tree, out)?; + } + Some(_) | None => (), + } + } + Ok(()) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::tests::{repo_init, write_commit_file}; - use pretty_assertions::{assert_eq, assert_ne}; + use super::*; + use crate::sync::tests::{repo_init, write_commit_file}; + use pretty_assertions::{assert_eq, assert_ne}; - #[test] - fn test_smoke() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_smoke() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let c1 = - write_commit_file(&repo, "test.txt", "content", "c1"); + let c1 = + write_commit_file(&repo, "test.txt", "content", "c1"); - let files = tree_files(repo_path, c1).unwrap(); + let files = tree_files(repo_path, c1).unwrap(); - assert_eq!(files.len(), 1); - assert_eq!(files[0].path, PathBuf::from("./test.txt")); + assert_eq!(files.len(), 1); + assert_eq!(files[0].path, PathBuf::from("./test.txt")); - let c2 = - write_commit_file(&repo, "test.txt", "content2", "c2"); + let c2 = + write_commit_file(&repo, "test.txt", "content2", "c2"); - let content = - tree_file_content(repo_path, &files[0]).unwrap(); - assert_eq!(&content, "content"); + let content = + tree_file_content(repo_path, &files[0]).unwrap(); + assert_eq!(&content, "content"); - let files_c2 = tree_files(repo_path, c2).unwrap(); + let files_c2 = tree_files(repo_path, c2).unwrap(); - assert_eq!(files_c2.len(), 1); - assert_ne!(files_c2[0], files[0]); - } + assert_eq!(files_c2.len(), 1); + assert_ne!(files_c2[0], files[0]); + } - #[test] - fn test_sorting() { - let mut list = vec!["file", "folder/file", "folder/afile"] - .iter() - .map(|f| TreeFile { - path: PathBuf::from(f), - filemode: 0, - id: Oid::zero(), - }) - .collect(); + #[test] + fn test_sorting() { + let mut list = vec!["file", "folder/file", "folder/afile"] + .iter() + .map(|f| TreeFile { + path: PathBuf::from(f), + filemode: 0, + id: Oid::zero(), + }) + .collect(); - sort_file_list(&mut list); + sort_file_list(&mut list); - assert_eq!( - list.iter() - .map(|f| f.path.to_string_lossy()) - .collect::>(), - vec![ - String::from("folder/afile"), - String::from("folder/file"), - String::from("file") - ] - ); - } + assert_eq!( + list.iter() + .map(|f| f.path.to_string_lossy()) + .collect::>(), + vec![ + String::from("folder/afile"), + String::from("folder/file"), + String::from("file") + ] + ); + } - #[test] - fn test_sorting_folders() { - let mut list = vec!["bfolder/file", "afolder/file"] - .iter() - .map(|f| TreeFile { - path: PathBuf::from(f), - filemode: 0, - id: Oid::zero(), - }) - .collect(); + #[test] + fn test_sorting_folders() { + let mut list = vec!["bfolder/file", "afolder/file"] + .iter() + .map(|f| TreeFile { + path: PathBuf::from(f), + filemode: 0, + id: Oid::zero(), + }) + .collect(); - sort_file_list(&mut list); + sort_file_list(&mut list); - assert_eq!( - list.iter() - .map(|f| f.path.to_string_lossy()) - .collect::>(), - vec![ - String::from("afolder/file"), - String::from("bfolder/file"), - ] - ); - } + assert_eq!( + list.iter() + .map(|f| f.path.to_string_lossy()) + .collect::>(), + vec![ + String::from("afolder/file"), + String::from("bfolder/file"), + ] + ); + } - #[test] - fn test_sorting_folders2() { - let mut list = vec!["bfolder/sub/file", "afolder/file"] - .iter() - .map(|f| TreeFile { - path: PathBuf::from(f), - filemode: 0, - id: Oid::zero(), - }) - .collect(); + #[test] + fn test_sorting_folders2() { + let mut list = vec!["bfolder/sub/file", "afolder/file"] + .iter() + .map(|f| TreeFile { + path: PathBuf::from(f), + filemode: 0, + id: Oid::zero(), + }) + .collect(); - sort_file_list(&mut list); + sort_file_list(&mut list); - assert_eq!( - list.iter() - .map(|f| f.path.to_string_lossy()) - .collect::>(), - vec![ - String::from("afolder/file"), - String::from("bfolder/sub/file"), - ] - ); - } + assert_eq!( + list.iter() + .map(|f| f.path.to_string_lossy()) + .collect::>(), + vec![ + String::from("afolder/file"), + String::from("bfolder/sub/file"), + ] + ); + } - #[test] - fn test_path_cmp() { - assert_eq!( - path_cmp( - &PathBuf::from("bfolder/sub/file"), - &PathBuf::from("afolder/file") - ), - Ordering::Greater - ); - } + #[test] + fn test_path_cmp() { + assert_eq!( + path_cmp( + &PathBuf::from("bfolder/sub/file"), + &PathBuf::from("afolder/file") + ), + Ordering::Greater + ); + } - #[test] - fn test_path_file_cmp() { - assert_eq!( - path_cmp( - &PathBuf::from("a"), - &PathBuf::from("afolder/file") - ), - Ordering::Greater - ); - } + #[test] + fn test_path_file_cmp() { + assert_eq!( + path_cmp( + &PathBuf::from("a"), + &PathBuf::from("afolder/file") + ), + Ordering::Greater + ); + } } diff --git a/asyncgit/src/sync/utils.rs b/asyncgit/src/sync/utils.rs index 1fbafff5..887194be 100644 --- a/asyncgit/src/sync/utils.rs +++ b/asyncgit/src/sync/utils.rs @@ -2,450 +2,450 @@ use super::CommitId; use crate::{ - error::{Error, Result}, - sync::config::untracked_files_config_repo, + error::{Error, Result}, + sync::config::untracked_files_config_repo, }; use git2::{IndexAddOption, Repository, RepositoryOpenFlags}; use scopetime::scope_time; use std::{ - fs::File, - io::Write, - path::{Path, PathBuf}, + fs::File, + io::Write, + path::{Path, PathBuf}, }; /// #[derive(PartialEq, Debug, Clone)] pub struct Head { - /// - pub name: String, - /// - pub id: CommitId, + /// + pub name: String, + /// + pub id: CommitId, } /// pub fn is_repo(repo_path: &str) -> bool { - Repository::open_ext( - repo_path, - RepositoryOpenFlags::empty(), - Vec::<&Path>::new(), - ) - .is_ok() + Repository::open_ext( + repo_path, + RepositoryOpenFlags::empty(), + Vec::<&Path>::new(), + ) + .is_ok() } /// checks if the git repo at path `repo_path` is a bare repo pub fn is_bare_repo(repo_path: &str) -> Result { - let repo = Repository::open_ext( - repo_path, - RepositoryOpenFlags::empty(), - Vec::<&Path>::new(), - )?; + let repo = Repository::open_ext( + repo_path, + RepositoryOpenFlags::empty(), + Vec::<&Path>::new(), + )?; - Ok(repo.is_bare()) + Ok(repo.is_bare()) } /// pub(crate) fn repo(repo_path: &str) -> Result { - let repo = Repository::open_ext( - repo_path, - RepositoryOpenFlags::empty(), - Vec::<&Path>::new(), - )?; + let repo = Repository::open_ext( + repo_path, + RepositoryOpenFlags::empty(), + Vec::<&Path>::new(), + )?; - if repo.is_bare() { - return Err(Error::Generic("bare repo".to_string())); - } + if repo.is_bare() { + return Err(Error::Generic("bare repo".to_string())); + } - Ok(repo) + Ok(repo) } /// pub(crate) fn work_dir(repo: &Repository) -> Result<&Path> { - repo.workdir().ok_or(Error::NoWorkDir) + repo.workdir().ok_or(Error::NoWorkDir) } /// path to .git folder pub fn repo_dir(repo_path: &str) -> Result { - let repo = repo(repo_path)?; - Ok(repo.path().to_owned()) + let repo = repo(repo_path)?; + Ok(repo.path().to_owned()) } /// pub fn repo_work_dir(repo_path: &str) -> Result { - let repo = repo(repo_path)?; - work_dir(&repo)?.to_str().map_or_else( - || Err(Error::Generic("invalid workdir".to_string())), - |workdir| Ok(workdir.to_string()), - ) + let repo = repo(repo_path)?; + work_dir(&repo)?.to_str().map_or_else( + || Err(Error::Generic("invalid workdir".to_string())), + |workdir| Ok(workdir.to_string()), + ) } /// pub fn get_head(repo_path: &str) -> Result { - let repo = repo(repo_path)?; - get_head_repo(&repo) + let repo = repo(repo_path)?; + get_head_repo(&repo) } /// pub fn get_head_tuple(repo_path: &str) -> Result { - let repo = repo(repo_path)?; - let id = get_head_repo(&repo)?; - let name = get_head_refname(&repo)?; + let repo = repo(repo_path)?; + let id = get_head_repo(&repo)?; + let name = get_head_refname(&repo)?; - Ok(Head { name, id }) + Ok(Head { name, id }) } /// pub fn get_head_refname(repo: &Repository) -> Result { - let head = repo.head()?; - let ref_name = bytes2string(head.name_bytes())?; + let head = repo.head()?; + let ref_name = bytes2string(head.name_bytes())?; - Ok(ref_name) + Ok(ref_name) } /// pub fn get_head_repo(repo: &Repository) -> Result { - scope_time!("get_head_repo"); + scope_time!("get_head_repo"); - let head = repo.head()?.target(); + let head = repo.head()?.target(); - head.map_or(Err(Error::NoHead), |head_id| Ok(head_id.into())) + head.map_or(Err(Error::NoHead), |head_id| Ok(head_id.into())) } /// add a file diff from workingdir to stage (will not add removed files see `stage_addremoved`) pub fn stage_add_file(repo_path: &str, path: &Path) -> Result<()> { - scope_time!("stage_add_file"); + scope_time!("stage_add_file"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let mut index = repo.index()?; + let mut index = repo.index()?; - index.add_path(path)?; - index.write()?; + index.add_path(path)?; + index.write()?; - Ok(()) + Ok(()) } /// like `stage_add_file` but uses a pattern to match/glob multiple files/folders pub fn stage_add_all(repo_path: &str, pattern: &str) -> Result<()> { - scope_time!("stage_add_all"); + scope_time!("stage_add_all"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let mut index = repo.index()?; + let mut index = repo.index()?; - let config = untracked_files_config_repo(&repo)?; + let config = untracked_files_config_repo(&repo)?; - if config.include_none() { - index.update_all(vec![pattern], None)?; - } else { - index.add_all( - vec![pattern], - IndexAddOption::DEFAULT, - None, - )?; - } + if config.include_none() { + index.update_all(vec![pattern], None)?; + } else { + index.add_all( + vec![pattern], + IndexAddOption::DEFAULT, + None, + )?; + } - index.write()?; + index.write()?; - Ok(()) + Ok(()) } /// Undo last commit in repo pub fn undo_last_commit(repo_path: &str) -> Result<()> { - let repo = repo(repo_path)?; - let previous_commit = repo.revparse_single("HEAD~")?; + let repo = repo(repo_path)?; + let previous_commit = repo.revparse_single("HEAD~")?; - Repository::reset( - &repo, - &previous_commit, - git2::ResetType::Soft, - None, - )?; + Repository::reset( + &repo, + &previous_commit, + git2::ResetType::Soft, + None, + )?; - Ok(()) + Ok(()) } /// stage a removed file pub fn stage_addremoved(repo_path: &str, path: &Path) -> Result<()> { - scope_time!("stage_addremoved"); + scope_time!("stage_addremoved"); - let repo = repo(repo_path)?; + let repo = repo(repo_path)?; - let mut index = repo.index()?; + let mut index = repo.index()?; - index.remove_path(path)?; - index.write()?; + index.remove_path(path)?; + index.write()?; - Ok(()) + Ok(()) } pub(crate) fn bytes2string(bytes: &[u8]) -> Result { - Ok(String::from_utf8(bytes.to_vec())?) + Ok(String::from_utf8(bytes.to_vec())?) } /// write a file in repo pub(crate) fn repo_write_file( - repo: &Repository, - file: &str, - content: &str, + repo: &Repository, + file: &str, + content: &str, ) -> Result<()> { - let dir = work_dir(repo)?.join(file); - let file_path = dir.to_str().ok_or_else(|| { - Error::Generic(String::from("invalid file path")) - })?; - let mut file = File::create(file_path)?; - file.write_all(content.as_bytes())?; - Ok(()) + let dir = work_dir(repo)?.join(file); + let file_path = dir.to_str().ok_or_else(|| { + Error::Generic(String::from("invalid file path")) + })?; + let mut file = File::create(file_path)?; + file.write_all(content.as_bytes())?; + Ok(()) } #[cfg(test)] pub(crate) fn repo_read_file( - repo: &Repository, - file: &str, + repo: &Repository, + file: &str, ) -> Result { - use std::io::Read; + use std::io::Read; - let dir = work_dir(repo)?.join(file); - let file_path = dir.to_str().ok_or_else(|| { - Error::Generic(String::from("invalid file path")) - })?; + let dir = work_dir(repo)?.join(file); + let file_path = dir.to_str().ok_or_else(|| { + Error::Generic(String::from("invalid file path")) + })?; - let mut file = File::open(file_path)?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer)?; + let mut file = File::open(file_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; - Ok(String::from_utf8(buffer)?) + Ok(String::from_utf8(buffer)?) } #[cfg(test)] mod tests { - use super::*; - use crate::sync::{ - commit, - diff::get_diff, - status::{get_status, StatusType}, - tests::{ - debug_cmd_print, get_statuses, repo_init, - repo_init_empty, write_commit_file, - }, - }; - use std::{ - fs::{self, remove_file, File}, - io::Write, - path::Path, - }; + use super::*; + use crate::sync::{ + commit, + diff::get_diff, + status::{get_status, StatusType}, + tests::{ + debug_cmd_print, get_statuses, repo_init, + repo_init_empty, write_commit_file, + }, + }; + use std::{ + fs::{self, remove_file, File}, + io::Write, + path::Path, + }; - #[test] - fn test_stage_add_smoke() { - let file_path = Path::new("foo"); - let (_td, repo) = repo_init_empty().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_stage_add_smoke() { + let file_path = Path::new("foo"); + let (_td, repo) = repo_init_empty().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!( - stage_add_file(repo_path, file_path).is_ok(), - false - ); - } + assert_eq!( + stage_add_file(repo_path, file_path).is_ok(), + false + ); + } - #[test] - fn test_staging_one_file() { - let file_path = Path::new("file1.txt"); - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_staging_one_file() { + let file_path = Path::new("file1.txt"); + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - File::create(&root.join(file_path)) - .unwrap() - .write_all(b"test file1 content") - .unwrap(); + File::create(&root.join(file_path)) + .unwrap() + .write_all(b"test file1 content") + .unwrap(); - File::create(&root.join(Path::new("file2.txt"))) - .unwrap() - .write_all(b"test file2 content") - .unwrap(); + File::create(&root.join(Path::new("file2.txt"))) + .unwrap() + .write_all(b"test file2 content") + .unwrap(); - assert_eq!(get_statuses(repo_path), (2, 0)); + assert_eq!(get_statuses(repo_path), (2, 0)); - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - assert_eq!(get_statuses(repo_path), (1, 1)); - } + assert_eq!(get_statuses(repo_path), (1, 1)); + } - #[test] - fn test_staging_folder() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_staging_folder() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let status_count = |s: StatusType| -> usize { - get_status(repo_path, s).unwrap().len() - }; + let status_count = |s: StatusType| -> usize { + get_status(repo_path, s).unwrap().len() + }; - fs::create_dir_all(&root.join("a/d"))?; - File::create(&root.join(Path::new("a/d/f1.txt")))? - .write_all(b"foo")?; - File::create(&root.join(Path::new("a/d/f2.txt")))? - .write_all(b"foo")?; - File::create(&root.join(Path::new("a/f3.txt")))? - .write_all(b"foo")?; + fs::create_dir_all(&root.join("a/d"))?; + File::create(&root.join(Path::new("a/d/f1.txt")))? + .write_all(b"foo")?; + File::create(&root.join(Path::new("a/d/f2.txt")))? + .write_all(b"foo")?; + File::create(&root.join(Path::new("a/f3.txt")))? + .write_all(b"foo")?; - assert_eq!(status_count(StatusType::WorkingDir), 3); + assert_eq!(status_count(StatusType::WorkingDir), 3); - stage_add_all(repo_path, "a/d").unwrap(); + stage_add_all(repo_path, "a/d").unwrap(); - assert_eq!(status_count(StatusType::WorkingDir), 1); - assert_eq!(status_count(StatusType::Stage), 2); + assert_eq!(status_count(StatusType::WorkingDir), 1); + assert_eq!(status_count(StatusType::Stage), 2); - Ok(()) - } + Ok(()) + } - #[test] - fn test_undo_commit_empty_repo() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_undo_commit_empty_repo() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - // expect to fail - assert!(undo_last_commit(repo_path).is_err()); - } + // expect to fail + assert!(undo_last_commit(repo_path).is_err()); + } - #[test] - fn test_undo_commit() { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_undo_commit() { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - // write commit file test.txt - let c1 = - write_commit_file(&repo, "test.txt", "content1", "c1"); - let _c2 = - write_commit_file(&repo, "test.txt", "content2", "c2"); - assert!(undo_last_commit(repo_path).is_ok()); + // write commit file test.txt + let c1 = + write_commit_file(&repo, "test.txt", "content1", "c1"); + let _c2 = + write_commit_file(&repo, "test.txt", "content2", "c2"); + assert!(undo_last_commit(repo_path).is_ok()); - // Make sure that HEAD points to c1 - assert_eq!(c1, get_head_repo(&repo).unwrap()); + // Make sure that HEAD points to c1 + assert_eq!(c1, get_head_repo(&repo).unwrap()); - // Make sure that now we have 1 file staged - assert_eq!(get_statuses(repo_path), (0, 1)); + // Make sure that now we have 1 file staged + assert_eq!(get_statuses(repo_path), (0, 1)); - // And that file is test.txt - let diff = get_diff(repo_path, "test.txt", true).unwrap(); - assert_eq!( - diff.hunks[0].lines[0].content, - String::from("@@ -1 +1 @@\n") - ); - } + // And that file is test.txt + let diff = get_diff(repo_path, "test.txt", true).unwrap(); + assert_eq!( + diff.hunks[0].lines[0].content, + String::from("@@ -1 +1 @@\n") + ); + } - #[test] - fn test_not_staging_untracked_folder() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_not_staging_untracked_folder() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - fs::create_dir_all(&root.join("a/d"))?; - File::create(&root.join(Path::new("a/d/f1.txt")))? - .write_all(b"foo")?; - File::create(&root.join(Path::new("a/d/f2.txt")))? - .write_all(b"foo")?; - File::create(&root.join(Path::new("f3.txt")))? - .write_all(b"foo")?; + fs::create_dir_all(&root.join("a/d"))?; + File::create(&root.join(Path::new("a/d/f1.txt")))? + .write_all(b"foo")?; + File::create(&root.join(Path::new("a/d/f2.txt")))? + .write_all(b"foo")?; + File::create(&root.join(Path::new("f3.txt")))? + .write_all(b"foo")?; - assert_eq!(get_statuses(repo_path), (3, 0)); + assert_eq!(get_statuses(repo_path), (3, 0)); - repo.config()?.set_str("status.showUntrackedFiles", "no")?; + repo.config()?.set_str("status.showUntrackedFiles", "no")?; - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - stage_add_all(repo_path, "*").unwrap(); + stage_add_all(repo_path, "*").unwrap(); - assert_eq!(get_statuses(repo_path), (0, 0)); + assert_eq!(get_statuses(repo_path), (0, 0)); - Ok(()) - } + Ok(()) + } - #[test] - fn test_staging_deleted_file() { - let file_path = Path::new("file1.txt"); - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_staging_deleted_file() { + let file_path = Path::new("file1.txt"); + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let status_count = |s: StatusType| -> usize { - get_status(repo_path, s).unwrap().len() - }; + let status_count = |s: StatusType| -> usize { + get_status(repo_path, s).unwrap().len() + }; - let full_path = &root.join(file_path); + let full_path = &root.join(file_path); - File::create(full_path) - .unwrap() - .write_all(b"test file1 content") - .unwrap(); + File::create(full_path) + .unwrap() + .write_all(b"test file1 content") + .unwrap(); - stage_add_file(repo_path, file_path).unwrap(); + stage_add_file(repo_path, file_path).unwrap(); - commit(repo_path, "commit msg").unwrap(); + commit(repo_path, "commit msg").unwrap(); - // delete the file now - assert_eq!(remove_file(full_path).is_ok(), true); + // delete the file now + assert_eq!(remove_file(full_path).is_ok(), true); - // deleted file in diff now - assert_eq!(status_count(StatusType::WorkingDir), 1); + // deleted file in diff now + assert_eq!(status_count(StatusType::WorkingDir), 1); - stage_addremoved(repo_path, file_path).unwrap(); + stage_addremoved(repo_path, file_path).unwrap(); - assert_eq!(status_count(StatusType::WorkingDir), 0); - assert_eq!(status_count(StatusType::Stage), 1); - } + assert_eq!(status_count(StatusType::WorkingDir), 0); + assert_eq!(status_count(StatusType::Stage), 1); + } - // see https://github.com/extrawurst/gitui/issues/108 - #[test] - fn test_staging_sub_git_folder() -> Result<()> { - let (_td, repo) = repo_init().unwrap(); - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + // see https://github.com/extrawurst/gitui/issues/108 + #[test] + fn test_staging_sub_git_folder() -> Result<()> { + let (_td, repo) = repo_init().unwrap(); + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - let status_count = |s: StatusType| -> usize { - get_status(repo_path, s).unwrap().len() - }; + let status_count = |s: StatusType| -> usize { + get_status(repo_path, s).unwrap().len() + }; - let sub = &root.join("sub"); + let sub = &root.join("sub"); - fs::create_dir_all(sub)?; + fs::create_dir_all(sub)?; - debug_cmd_print(sub.to_str().unwrap(), "git init subgit"); + debug_cmd_print(sub.to_str().unwrap(), "git init subgit"); - File::create(sub.join("subgit/foo.txt")) - .unwrap() - .write_all(b"content") - .unwrap(); + File::create(sub.join("subgit/foo.txt")) + .unwrap() + .write_all(b"content") + .unwrap(); - assert_eq!(status_count(StatusType::WorkingDir), 1); + assert_eq!(status_count(StatusType::WorkingDir), 1); - //expect to fail - assert!(stage_add_all(repo_path, "sub").is_err()); + //expect to fail + assert!(stage_add_all(repo_path, "sub").is_err()); - Ok(()) - } + Ok(()) + } - #[test] - fn test_head_empty() -> Result<()> { - let (_td, repo) = repo_init_empty()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_head_empty() -> Result<()> { + let (_td, repo) = repo_init_empty()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_head(repo_path).is_ok(), false); + assert_eq!(get_head(repo_path).is_ok(), false); - Ok(()) - } + Ok(()) + } - #[test] - fn test_head() -> Result<()> { - let (_td, repo) = repo_init()?; - let root = repo.path().parent().unwrap(); - let repo_path = root.as_os_str().to_str().unwrap(); + #[test] + fn test_head() -> Result<()> { + let (_td, repo) = repo_init()?; + let root = repo.path().parent().unwrap(); + let repo_path = root.as_os_str().to_str().unwrap(); - assert_eq!(get_head(repo_path).is_ok(), true); + assert_eq!(get_head(repo_path).is_ok(), true); - Ok(()) - } + Ok(()) + } } diff --git a/asyncgit/src/tags.rs b/asyncgit/src/tags.rs index 6581e40f..544e9f3a 100644 --- a/asyncgit/src/tags.rs +++ b/asyncgit/src/tags.rs @@ -1,135 +1,135 @@ use crate::{ - error::Result, - hash, - sync::{self}, - AsyncGitNotification, CWD, + error::Result, + hash, + sync::{self}, + AsyncGitNotification, CWD, }; use crossbeam_channel::Sender; use std::{ - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, - }, - time::{Duration, Instant}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, + time::{Duration, Instant}, }; use sync::Tags; /// #[derive(Default, Clone)] struct TagsResult { - hash: u64, - tags: Tags, + hash: u64, + tags: Tags, } /// pub struct AsyncTags { - last: Arc>>, - sender: Sender, - pending: Arc, + last: Arc>>, + sender: Sender, + pending: Arc, } impl AsyncTags { - /// - pub fn new(sender: &Sender) -> Self { - Self { - last: Arc::new(Mutex::new(None)), - sender: sender.clone(), - pending: Arc::new(AtomicUsize::new(0)), - } - } + /// + pub fn new(sender: &Sender) -> Self { + Self { + last: Arc::new(Mutex::new(None)), + sender: sender.clone(), + pending: Arc::new(AtomicUsize::new(0)), + } + } - /// last fetched result - pub fn last(&mut self) -> Result> { - let last = self.last.lock()?; + /// last fetched result + pub fn last(&mut self) -> Result> { + let last = self.last.lock()?; - Ok(last.clone().map(|last| last.1.tags)) - } + Ok(last.clone().map(|last| last.1.tags)) + } - /// - pub fn is_pending(&self) -> bool { - self.pending.load(Ordering::Relaxed) > 0 - } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) > 0 + } - fn is_outdated(&self, dur: Duration) -> Result { - let last = self.last.lock()?; + fn is_outdated(&self, dur: Duration) -> Result { + let last = self.last.lock()?; - Ok(last - .as_ref() - .map_or(true, |(last_time, _)| last_time.elapsed() > dur)) - } + Ok(last + .as_ref() + .map_or(true, |(last_time, _)| last_time.elapsed() > dur)) + } - /// - pub fn request( - &mut self, - dur: Duration, - force: bool, - ) -> Result<()> { - log::trace!("request"); + /// + pub fn request( + &mut self, + dur: Duration, + force: bool, + ) -> Result<()> { + log::trace!("request"); - if !force && self.is_pending() { - return Ok(()); - } + if !force && self.is_pending() { + return Ok(()); + } - let outdated = self.is_outdated(dur)?; + let outdated = self.is_outdated(dur)?; - if !force && !outdated { - return Ok(()); - } + if !force && !outdated { + return Ok(()); + } - let arc_last = Arc::clone(&self.last); - let sender = self.sender.clone(); - let arc_pending = Arc::clone(&self.pending); + let arc_last = Arc::clone(&self.last); + let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); - self.pending.fetch_add(1, Ordering::Relaxed); + self.pending.fetch_add(1, Ordering::Relaxed); - rayon_core::spawn(move || { - let notify = Self::getter(&arc_last, outdated) - .expect("error getting tags"); + rayon_core::spawn(move || { + let notify = Self::getter(&arc_last, outdated) + .expect("error getting tags"); - arc_pending.fetch_sub(1, Ordering::Relaxed); + arc_pending.fetch_sub(1, Ordering::Relaxed); - sender - .send(if notify { - AsyncGitNotification::Tags - } else { - AsyncGitNotification::FinishUnchanged - }) - .expect("error sending notify"); - }); + sender + .send(if notify { + AsyncGitNotification::Tags + } else { + AsyncGitNotification::FinishUnchanged + }) + .expect("error sending notify"); + }); - Ok(()) - } + Ok(()) + } - fn getter( - arc_last: &Arc>>, - outdated: bool, - ) -> Result { - let tags = sync::get_tags(CWD)?; + fn getter( + arc_last: &Arc>>, + outdated: bool, + ) -> Result { + let tags = sync::get_tags(CWD)?; - let hash = hash(&tags); + let hash = hash(&tags); - if !outdated - && Self::last_hash(arc_last) - .map(|last| last == hash) - .unwrap_or_default() - { - return Ok(false); - } + if !outdated + && Self::last_hash(arc_last) + .map(|last| last == hash) + .unwrap_or_default() + { + return Ok(false); + } - { - let mut last = arc_last.lock()?; - let now = Instant::now(); - *last = Some((now, TagsResult { hash, tags })); - } + { + let mut last = arc_last.lock()?; + let now = Instant::now(); + *last = Some((now, TagsResult { hash, tags })); + } - Ok(true) - } + Ok(true) + } - fn last_hash( - last: &Arc>>, - ) -> Option { - last.lock() - .ok() - .and_then(|last| last.as_ref().map(|(_, last)| last.hash)) - } + fn last_hash( + last: &Arc>>, + ) -> Option { + last.lock() + .ok() + .and_then(|last| last.as_ref().map(|(_, last)| last.hash)) + } } diff --git a/filetreelist/src/error.rs b/filetreelist/src/error.rs index b4250df0..068db8b4 100644 --- a/filetreelist/src/error.rs +++ b/filetreelist/src/error.rs @@ -5,11 +5,11 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { - #[error("InvalidPath: `{0}`")] - InvalidPath(PathBuf), + #[error("InvalidPath: `{0}`")] + InvalidPath(PathBuf), - #[error("TryFromInt error:{0}")] - IntConversion(#[from] TryFromIntError), + #[error("TryFromInt error:{0}")] + IntConversion(#[from] TryFromIntError), } pub type Result = std::result::Result; diff --git a/filetreelist/src/filetree.rs b/filetreelist/src/filetree.rs index d0a19a6b..17db731e 100644 --- a/filetreelist/src/filetree.rs +++ b/filetreelist/src/filetree.rs @@ -1,497 +1,497 @@ use crate::{ - error::Result, filetreeitems::FileTreeItems, - tree_iter::TreeIterator, TreeItemInfo, + error::Result, filetreeitems::FileTreeItems, + tree_iter::TreeIterator, TreeItemInfo, }; use std::{collections::BTreeSet, path::Path, usize}; /// #[derive(Copy, Clone, Debug)] pub enum MoveSelection { - Up, - Down, - Left, - Right, - Top, - End, - PageDown, - PageUp, + Up, + Down, + Left, + Right, + Top, + End, + PageDown, + PageUp, } #[derive(Debug, Clone, Copy)] pub struct VisualSelection { - pub count: usize, - pub index: usize, + pub count: usize, + pub index: usize, } /// wraps `FileTreeItems` as a datastore and adds selection functionality #[derive(Default)] pub struct FileTree { - items: FileTreeItems, - selection: Option, - // caches the absolute selection translated to visual index - visual_selection: Option, + items: FileTreeItems, + selection: Option, + // caches the absolute selection translated to visual index + visual_selection: Option, } impl FileTree { - /// - pub fn new( - list: &[&Path], - collapsed: &BTreeSet<&String>, - ) -> Result { - let mut new_self = Self { - items: FileTreeItems::new(list, collapsed)?, - selection: if list.is_empty() { None } else { Some(0) }, - visual_selection: None, - }; - new_self.visual_selection = new_self.calc_visual_selection(); + /// + pub fn new( + list: &[&Path], + collapsed: &BTreeSet<&String>, + ) -> Result { + let mut new_self = Self { + items: FileTreeItems::new(list, collapsed)?, + selection: if list.is_empty() { None } else { Some(0) }, + visual_selection: None, + }; + new_self.visual_selection = new_self.calc_visual_selection(); - Ok(new_self) - } + Ok(new_self) + } - /// - pub const fn is_empty(&self) -> bool { - self.items.file_count() == 0 - } + /// + pub const fn is_empty(&self) -> bool { + self.items.file_count() == 0 + } - /// - pub fn collapse_but_root(&mut self) { - if !self.is_empty() { - self.items.collapse(0, true); - self.items.expand(0, false); - } - } + /// + pub fn collapse_but_root(&mut self) { + if !self.is_empty() { + self.items.collapse(0, true); + self.items.expand(0, false); + } + } - /// iterates visible elements starting from `start_index_visual` - pub fn iterate( - &self, - start_index_visual: usize, - max_amount: usize, - ) -> TreeIterator<'_> { - let start = self - .visual_index_to_absolute(start_index_visual) - .unwrap_or_default(); - TreeIterator::new( - self.items.iterate(start, max_amount), - self.selection, - ) - } + /// iterates visible elements starting from `start_index_visual` + pub fn iterate( + &self, + start_index_visual: usize, + max_amount: usize, + ) -> TreeIterator<'_> { + let start = self + .visual_index_to_absolute(start_index_visual) + .unwrap_or_default(); + TreeIterator::new( + self.items.iterate(start, max_amount), + self.selection, + ) + } - /// - pub const fn visual_selection(&self) -> Option<&VisualSelection> { - self.visual_selection.as_ref() - } + /// + pub const fn visual_selection(&self) -> Option<&VisualSelection> { + self.visual_selection.as_ref() + } - /// - pub fn selected_file(&self) -> Option<&TreeItemInfo> { - self.selection.and_then(|index| { - let item = &self.items.tree_items[index]; - if item.kind().is_path() { - None - } else { - Some(item.info()) - } - }) - } + /// + pub fn selected_file(&self) -> Option<&TreeItemInfo> { + self.selection.and_then(|index| { + let item = &self.items.tree_items[index]; + if item.kind().is_path() { + None + } else { + Some(item.info()) + } + }) + } - /// - pub fn collapse_recursive(&mut self) { - if let Some(selection) = self.selection { - self.items.collapse(selection, true); - } - } + /// + pub fn collapse_recursive(&mut self) { + if let Some(selection) = self.selection { + self.items.collapse(selection, true); + } + } - /// - pub fn expand_recursive(&mut self) { - if let Some(selection) = self.selection { - self.items.expand(selection, true); - } - } + /// + pub fn expand_recursive(&mut self) { + if let Some(selection) = self.selection { + self.items.expand(selection, true); + } + } - /// - pub fn move_selection(&mut self, dir: MoveSelection) -> bool { - self.selection.map_or(false, |selection| { - let new_index = match dir { - MoveSelection::Up => { - self.selection_updown(selection, true) - } - MoveSelection::Down => { - self.selection_updown(selection, false) - } - MoveSelection::Left => self.selection_left(selection), - MoveSelection::Right => { - self.selection_right(selection) - } - MoveSelection::Top => { - Self::selection_start(selection) - } - MoveSelection::End => self.selection_end(selection), - MoveSelection::PageDown | MoveSelection::PageUp => { - None - } - }; + /// + pub fn move_selection(&mut self, dir: MoveSelection) -> bool { + self.selection.map_or(false, |selection| { + let new_index = match dir { + MoveSelection::Up => { + self.selection_updown(selection, true) + } + MoveSelection::Down => { + self.selection_updown(selection, false) + } + MoveSelection::Left => self.selection_left(selection), + MoveSelection::Right => { + self.selection_right(selection) + } + MoveSelection::Top => { + Self::selection_start(selection) + } + MoveSelection::End => self.selection_end(selection), + MoveSelection::PageDown | MoveSelection::PageUp => { + None + } + }; - let changed_index = - new_index.map(|i| i != selection).unwrap_or_default(); + let changed_index = + new_index.map(|i| i != selection).unwrap_or_default(); - if changed_index { - self.selection = new_index; - self.visual_selection = self.calc_visual_selection(); - } + if changed_index { + self.selection = new_index; + self.visual_selection = self.calc_visual_selection(); + } - changed_index || new_index.is_some() - }) - } + changed_index || new_index.is_some() + }) + } - fn visual_index_to_absolute( - &self, - visual_index: usize, - ) -> Option { - self.items - .iterate(0, self.items.len()) - .enumerate() - .find_map(|(i, (abs, _))| { - if i == visual_index { - Some(abs) - } else { - None - } - }) - } + fn visual_index_to_absolute( + &self, + visual_index: usize, + ) -> Option { + self.items + .iterate(0, self.items.len()) + .enumerate() + .find_map(|(i, (abs, _))| { + if i == visual_index { + Some(abs) + } else { + None + } + }) + } - fn calc_visual_selection(&self) -> Option { - self.selection.map(|selection_absolute| { - let mut count = 0; - let mut visual_index = 0; - for (index, _item) in - self.items.iterate(0, self.items.len()) - { - if selection_absolute == index { - visual_index = count; - } + fn calc_visual_selection(&self) -> Option { + self.selection.map(|selection_absolute| { + let mut count = 0; + let mut visual_index = 0; + for (index, _item) in + self.items.iterate(0, self.items.len()) + { + if selection_absolute == index { + visual_index = count; + } - count += 1; - } + count += 1; + } - VisualSelection { - index: visual_index, - count, - } - }) - } + VisualSelection { + index: visual_index, + count, + } + }) + } - const fn selection_start(current_index: usize) -> Option { - if current_index == 0 { - None - } else { - Some(0) - } - } + const fn selection_start(current_index: usize) -> Option { + if current_index == 0 { + None + } else { + Some(0) + } + } - fn selection_end(&self, current_index: usize) -> Option { - let items_max = self.items.len().saturating_sub(1); + fn selection_end(&self, current_index: usize) -> Option { + let items_max = self.items.len().saturating_sub(1); - let mut new_index = items_max; + let mut new_index = items_max; - loop { - if self.is_visible_index(new_index) { - break; - } + loop { + if self.is_visible_index(new_index) { + break; + } - if new_index == 0 { - break; - } + if new_index == 0 { + break; + } - new_index = new_index.saturating_sub(1); - new_index = std::cmp::min(new_index, items_max); - } + new_index = new_index.saturating_sub(1); + new_index = std::cmp::min(new_index, items_max); + } - if new_index == current_index { - None - } else { - Some(new_index) - } - } + if new_index == current_index { + None + } else { + Some(new_index) + } + } - fn selection_updown( - &self, - current_index: usize, - up: bool, - ) -> Option { - let mut index = current_index; + fn selection_updown( + &self, + current_index: usize, + up: bool, + ) -> Option { + let mut index = current_index; - loop { - index = { - let new_index = if up { - index.saturating_sub(1) - } else { - index.saturating_add(1) - }; + loop { + index = { + let new_index = if up { + index.saturating_sub(1) + } else { + index.saturating_add(1) + }; - // when reaching usize bounds - if new_index == index { - break; - } + // when reaching usize bounds + if new_index == index { + break; + } - if new_index >= self.items.len() { - break; - } + if new_index >= self.items.len() { + break; + } - new_index - }; + new_index + }; - if self.is_visible_index(index) { - break; - } - } + if self.is_visible_index(index) { + break; + } + } - if index == current_index { - None - } else { - Some(index) - } - } + if index == current_index { + None + } else { + Some(index) + } + } - fn select_parent( - &mut self, - current_index: usize, - ) -> Option { - let indent = - self.items.tree_items[current_index].info().indent(); + fn select_parent( + &mut self, + current_index: usize, + ) -> Option { + let indent = + self.items.tree_items[current_index].info().indent(); - let mut index = current_index; + let mut index = current_index; - while let Some(selection) = self.selection_updown(index, true) - { - index = selection; + while let Some(selection) = self.selection_updown(index, true) + { + index = selection; - if self.items.tree_items[index].info().indent() < indent { - break; - } - } + if self.items.tree_items[index].info().indent() < indent { + break; + } + } - if index == current_index { - None - } else { - Some(index) - } - } + if index == current_index { + None + } else { + Some(index) + } + } - fn selection_left( - &mut self, - current_index: usize, - ) -> Option { - let item = &mut self.items.tree_items[current_index]; + fn selection_left( + &mut self, + current_index: usize, + ) -> Option { + let item = &mut self.items.tree_items[current_index]; - if item.kind().is_path() && !item.kind().is_path_collapsed() { - self.items.collapse(current_index, false); - return Some(current_index); - } + if item.kind().is_path() && !item.kind().is_path_collapsed() { + self.items.collapse(current_index, false); + return Some(current_index); + } - self.select_parent(current_index) - } + self.select_parent(current_index) + } - fn selection_right( - &mut self, - current_selection: usize, - ) -> Option { - let item = &mut self.items.tree_items[current_selection]; + fn selection_right( + &mut self, + current_selection: usize, + ) -> Option { + let item = &mut self.items.tree_items[current_selection]; - if item.kind().is_path() { - if item.kind().is_path_collapsed() { - self.items.expand(current_selection, false); - return Some(current_selection); - } - return self.selection_updown(current_selection, false); - } + if item.kind().is_path() { + if item.kind().is_path_collapsed() { + self.items.expand(current_selection, false); + return Some(current_selection); + } + return self.selection_updown(current_selection, false); + } - None - } + None + } - fn is_visible_index(&self, index: usize) -> bool { - self.items - .tree_items - .get(index) - .map(|item| item.info().is_visible()) - .unwrap_or_default() - } + fn is_visible_index(&self, index: usize) -> bool { + self.items + .tree_items + .get(index) + .map(|item| item.info().is_visible()) + .unwrap_or_default() + } } #[cfg(test)] mod test { - use crate::{FileTree, MoveSelection}; - use pretty_assertions::assert_eq; - use std::{collections::BTreeSet, path::Path}; + use crate::{FileTree, MoveSelection}; + use pretty_assertions::assert_eq; + use std::{collections::BTreeSet, path::Path}; - #[test] - fn test_selection() { - let items = vec![ - Path::new("a/b"), // - ]; + #[test] + fn test_selection() { + let items = vec![ + Path::new("a/b"), // + ]; - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - assert!(tree.move_selection(MoveSelection::Down)); + assert!(tree.move_selection(MoveSelection::Down)); - assert_eq!(tree.selection, Some(1)); + assert_eq!(tree.selection, Some(1)); - assert!(!tree.move_selection(MoveSelection::Down)); + assert!(!tree.move_selection(MoveSelection::Down)); - assert_eq!(tree.selection, Some(1)); - } + assert_eq!(tree.selection, Some(1)); + } - #[test] - fn test_selection_skips_collapsed() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; + #[test] + fn test_selection_skips_collapsed() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 d + //0 a/ + //1 b/ + //2 c + //3 d - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - tree.items.collapse(1, false); - tree.selection = Some(1); + tree.items.collapse(1, false); + tree.selection = Some(1); - assert!(tree.move_selection(MoveSelection::Down)); + assert!(tree.move_selection(MoveSelection::Down)); - assert_eq!(tree.selection, Some(3)); - } + assert_eq!(tree.selection, Some(3)); + } - #[test] - fn test_selection_left_collapse() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; + #[test] + fn test_selection_left_collapse() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 d + //0 a/ + //1 b/ + //2 c + //3 d - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - tree.selection = Some(1); + tree.selection = Some(1); - //collapses 1 - assert!(tree.move_selection(MoveSelection::Left)); - // index will not change - assert_eq!(tree.selection, Some(1)); + //collapses 1 + assert!(tree.move_selection(MoveSelection::Left)); + // index will not change + assert_eq!(tree.selection, Some(1)); - assert!(tree.items.tree_items[1].kind().is_path_collapsed()); - assert!(!tree.items.tree_items[2].info().is_visible()); - } + assert!(tree.items.tree_items[1].kind().is_path_collapsed()); + assert!(!tree.items.tree_items[2].info().is_visible()); + } - #[test] - fn test_selection_left_parent() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; + #[test] + fn test_selection_left_parent() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 d + //0 a/ + //1 b/ + //2 c + //3 d - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - tree.selection = Some(2); + tree.selection = Some(2); - assert!(tree.move_selection(MoveSelection::Left)); - assert_eq!(tree.selection, Some(1)); + assert!(tree.move_selection(MoveSelection::Left)); + assert_eq!(tree.selection, Some(1)); - assert!(tree.move_selection(MoveSelection::Left)); - assert_eq!(tree.selection, Some(1)); + assert!(tree.move_selection(MoveSelection::Left)); + assert_eq!(tree.selection, Some(1)); - assert!(tree.move_selection(MoveSelection::Left)); - assert_eq!(tree.selection, Some(0)); - } + assert!(tree.move_selection(MoveSelection::Left)); + assert_eq!(tree.selection, Some(0)); + } - #[test] - fn test_selection_right_expand() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; + #[test] + fn test_selection_right_expand() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 d + //0 a/ + //1 b/ + //2 c + //3 d - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - tree.items.collapse(1, false); - tree.items.collapse(0, false); - tree.selection = Some(0); + tree.items.collapse(1, false); + tree.items.collapse(0, false); + tree.selection = Some(0); - assert!(tree.move_selection(MoveSelection::Right)); - assert_eq!(tree.selection, Some(0)); - assert!(!tree.items.tree_items[0].kind().is_path_collapsed()); + assert!(tree.move_selection(MoveSelection::Right)); + assert_eq!(tree.selection, Some(0)); + assert!(!tree.items.tree_items[0].kind().is_path_collapsed()); - assert!(tree.move_selection(MoveSelection::Right)); - assert_eq!(tree.selection, Some(1)); - assert!(tree.items.tree_items[1].kind().is_path_collapsed()); + assert!(tree.move_selection(MoveSelection::Right)); + assert_eq!(tree.selection, Some(1)); + assert!(tree.items.tree_items[1].kind().is_path_collapsed()); - assert!(tree.move_selection(MoveSelection::Right)); - assert_eq!(tree.selection, Some(1)); - assert!(!tree.items.tree_items[1].kind().is_path_collapsed()); - } + assert!(tree.move_selection(MoveSelection::Right)); + assert_eq!(tree.selection, Some(1)); + assert!(!tree.items.tree_items[1].kind().is_path_collapsed()); + } - #[test] - fn test_selection_top() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; + #[test] + fn test_selection_top() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 d + //0 a/ + //1 b/ + //2 c + //3 d - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - tree.selection = Some(3); + tree.selection = Some(3); - assert!(tree.move_selection(MoveSelection::Top)); - assert_eq!(tree.selection, Some(0)); - } + assert!(tree.move_selection(MoveSelection::Top)); + assert_eq!(tree.selection, Some(0)); + } - #[test] - fn test_visible_selection() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/b/c2"), // - Path::new("a/d"), // - ]; + #[test] + fn test_visible_selection() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/b/c2"), // + Path::new("a/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 c2 - //4 d + //0 a/ + //1 b/ + //2 c + //3 c2 + //4 d - let mut tree = - FileTree::new(&items, &BTreeSet::new()).unwrap(); + let mut tree = + FileTree::new(&items, &BTreeSet::new()).unwrap(); - tree.selection = Some(1); - assert!(tree.move_selection(MoveSelection::Left)); - assert!(tree.move_selection(MoveSelection::Down)); - let s = tree.visual_selection().unwrap(); + tree.selection = Some(1); + assert!(tree.move_selection(MoveSelection::Left)); + assert!(tree.move_selection(MoveSelection::Down)); + let s = tree.visual_selection().unwrap(); - assert_eq!(s.count, 3); - assert_eq!(s.index, 2); - } + assert_eq!(s.count, 3); + assert_eq!(s.index, 2); + } } diff --git a/filetreelist/src/filetreeitems.rs b/filetreelist/src/filetreeitems.rs index dec003a9..b545f841 100644 --- a/filetreelist/src/filetreeitems.rs +++ b/filetreelist/src/filetreeitems.rs @@ -1,814 +1,814 @@ use crate::{ - error::Error, - item::{FileTreeItemKind, PathCollapsed}, - FileTreeItem, + error::Error, + item::{FileTreeItemKind, PathCollapsed}, + FileTreeItem, }; use crate::{error::Result, treeitems_iter::TreeItemsIterator}; use std::{ - collections::{BTreeSet, HashMap}, - path::{Path, PathBuf}, - usize, + collections::{BTreeSet, HashMap}, + path::{Path, PathBuf}, + usize, }; /// #[derive(Default)] pub struct FileTreeItems { - pub tree_items: Vec, - files: usize, + pub tree_items: Vec, + files: usize, } impl FileTreeItems { - /// - pub fn new( - list: &[&Path], - collapsed: &BTreeSet<&String>, - ) -> Result { - let (mut items, paths) = Self::create_items(list, collapsed)?; + /// + pub fn new( + list: &[&Path], + collapsed: &BTreeSet<&String>, + ) -> Result { + let (mut items, paths) = Self::create_items(list, collapsed)?; - Self::fold_paths(&mut items, &paths); + Self::fold_paths(&mut items, &paths); - Ok(Self { - tree_items: items, - files: list.len(), - }) - } + Ok(Self { + tree_items: items, + files: list.len(), + }) + } - fn create_items<'a>( - list: &'a [&Path], - collapsed: &BTreeSet<&String>, - ) -> Result<(Vec, HashMap<&'a Path, usize>)> { - // scopetime::scope_time!("create_items"); + fn create_items<'a>( + list: &'a [&Path], + collapsed: &BTreeSet<&String>, + ) -> Result<(Vec, HashMap<&'a Path, usize>)> { + // scopetime::scope_time!("create_items"); - let mut items = Vec::with_capacity(list.len()); - let mut paths_added: HashMap<&Path, usize> = - HashMap::with_capacity(list.len()); + let mut items = Vec::with_capacity(list.len()); + let mut paths_added: HashMap<&Path, usize> = + HashMap::with_capacity(list.len()); - for e in list { - { - Self::push_dirs( - e, - &mut items, - &mut paths_added, - collapsed, - )?; - } + for e in list { + { + Self::push_dirs( + e, + &mut items, + &mut paths_added, + collapsed, + )?; + } - items.push(FileTreeItem::new_file(e)?); - } + items.push(FileTreeItem::new_file(e)?); + } - Ok((items, paths_added)) - } + Ok((items, paths_added)) + } - /// how many individual items (files/paths) are in the list - pub fn len(&self) -> usize { - self.tree_items.len() - } + /// how many individual items (files/paths) are in the list + pub fn len(&self) -> usize { + self.tree_items.len() + } - /// how many files were added to this list - pub const fn file_count(&self) -> usize { - self.files - } + /// how many files were added to this list + pub const fn file_count(&self) -> usize { + self.files + } - /// iterates visible elements - pub const fn iterate( - &self, - start: usize, - max_amount: usize, - ) -> TreeItemsIterator<'_> { - TreeItemsIterator::new(self, start, max_amount) - } + /// iterates visible elements + pub const fn iterate( + &self, + start: usize, + max_amount: usize, + ) -> TreeItemsIterator<'_> { + TreeItemsIterator::new(self, start, max_amount) + } - fn push_dirs<'a>( - item_path: &'a Path, - nodes: &mut Vec, - // helps to only add new nodes for paths that were not added before - // we also count the number of children a node has for later folding - paths_added: &mut HashMap<&'a Path, usize>, - collapsed: &BTreeSet<&String>, - ) -> Result<()> { - let mut ancestors = - item_path.ancestors().skip(1).collect::>(); - ancestors.reverse(); + fn push_dirs<'a>( + item_path: &'a Path, + nodes: &mut Vec, + // helps to only add new nodes for paths that were not added before + // we also count the number of children a node has for later folding + paths_added: &mut HashMap<&'a Path, usize>, + collapsed: &BTreeSet<&String>, + ) -> Result<()> { + let mut ancestors = + item_path.ancestors().skip(1).collect::>(); + ancestors.reverse(); - for c in &ancestors { - if c.parent().is_some() && !paths_added.contains_key(c) { - // add node and set count to have no children - paths_added.insert(c, 0); + for c in &ancestors { + if c.parent().is_some() && !paths_added.contains_key(c) { + // add node and set count to have no children + paths_added.insert(c, 0); - // increase the number of children in the parent node count - if let Some(parent) = c.parent() { - if !parent.as_os_str().is_empty() { - *paths_added.entry(parent).or_insert(0) += 1; - } - } + // increase the number of children in the parent node count + if let Some(parent) = c.parent() { + if !parent.as_os_str().is_empty() { + *paths_added.entry(parent).or_insert(0) += 1; + } + } - //TODO: make non alloc - let path_string = Self::path_to_string(c)?; - let is_collapsed = collapsed.contains(&path_string); - nodes.push(FileTreeItem::new_path(c, is_collapsed)?); - } - } + //TODO: make non alloc + let path_string = Self::path_to_string(c)?; + let is_collapsed = collapsed.contains(&path_string); + nodes.push(FileTreeItem::new_path(c, is_collapsed)?); + } + } - // increase child count in parent node (the above ancenstor ignores the leaf component) - if let Some(parent) = item_path.parent() { - *paths_added.entry(parent).or_insert(0) += 1; - } + // increase child count in parent node (the above ancenstor ignores the leaf component) + if let Some(parent) = item_path.parent() { + *paths_added.entry(parent).or_insert(0) += 1; + } - Ok(()) - } + Ok(()) + } - //TODO: return ref - fn path_to_string(p: &Path) -> Result { - Ok(p.to_str() - .map_or_else( - || Err(Error::InvalidPath(p.to_path_buf())), - Ok, - )? - .to_string()) - } + //TODO: return ref + fn path_to_string(p: &Path) -> Result { + Ok(p.to_str() + .map_or_else( + || Err(Error::InvalidPath(p.to_path_buf())), + Ok, + )? + .to_string()) + } - pub fn collapse(&mut self, index: usize, recursive: bool) { - if self.tree_items[index].kind().is_path() { - self.tree_items[index].collapse_path(); + pub fn collapse(&mut self, index: usize, recursive: bool) { + if self.tree_items[index].kind().is_path() { + self.tree_items[index].collapse_path(); - let path = PathBuf::from( - self.tree_items[index].info().full_path_str(), - ); + let path = PathBuf::from( + self.tree_items[index].info().full_path_str(), + ); - for i in index + 1..self.tree_items.len() { - let item = &mut self.tree_items[i]; + for i in index + 1..self.tree_items.len() { + let item = &mut self.tree_items[i]; - if recursive && item.kind().is_path() { - item.collapse_path(); - } + if recursive && item.kind().is_path() { + item.collapse_path(); + } - let item_path = - Path::new(item.info().full_path_str()); + let item_path = + Path::new(item.info().full_path_str()); - if item_path.starts_with(&path) { - item.hide(); - } else { - return; - } - } - } - } + if item_path.starts_with(&path) { + item.hide(); + } else { + return; + } + } + } + } - pub fn expand(&mut self, index: usize, recursive: bool) { - if self.tree_items[index].kind().is_path() { - self.tree_items[index].expand_path(); + pub fn expand(&mut self, index: usize, recursive: bool) { + if self.tree_items[index].kind().is_path() { + self.tree_items[index].expand_path(); - let full_path = PathBuf::from( - self.tree_items[index].info().full_path_str(), - ); + let full_path = PathBuf::from( + self.tree_items[index].info().full_path_str(), + ); - if recursive { - for i in index + 1..self.tree_items.len() { - let item = &mut self.tree_items[i]; + if recursive { + for i in index + 1..self.tree_items.len() { + let item = &mut self.tree_items[i]; - if !Path::new(item.info().full_path_str()) - .starts_with(&full_path) - { - break; - } + if !Path::new(item.info().full_path_str()) + .starts_with(&full_path) + { + break; + } - if item.kind().is_path() - && item.kind().is_path_collapsed() - { - item.expand_path(); - } - } - } + if item.kind().is_path() + && item.kind().is_path_collapsed() + { + item.expand_path(); + } + } + } - self.update_visibility( - &Some(full_path), - index + 1, - false, - ); - } - } + self.update_visibility( + &Some(full_path), + index + 1, + false, + ); + } + } - fn update_visibility( - &mut self, - prefix: &Option, - start_idx: usize, - set_defaults: bool, - ) { - // if we are in any subpath that is collapsed we keep skipping over it - let mut inner_collapsed: Option = None; + fn update_visibility( + &mut self, + prefix: &Option, + start_idx: usize, + set_defaults: bool, + ) { + // if we are in any subpath that is collapsed we keep skipping over it + let mut inner_collapsed: Option = None; - for i in start_idx..self.tree_items.len() { - if let Some(ref collapsed_path) = inner_collapsed { - let p = Path::new( - self.tree_items[i].info().full_path_str(), - ); - if p.starts_with(collapsed_path) { - if set_defaults { - self.tree_items[i] - .info_mut() - .set_visible(false); - } - // we are still in a collapsed inner path - continue; - } - inner_collapsed = None; - } + for i in start_idx..self.tree_items.len() { + if let Some(ref collapsed_path) = inner_collapsed { + let p = Path::new( + self.tree_items[i].info().full_path_str(), + ); + if p.starts_with(collapsed_path) { + if set_defaults { + self.tree_items[i] + .info_mut() + .set_visible(false); + } + // we are still in a collapsed inner path + continue; + } + inner_collapsed = None; + } - let item_kind = self.tree_items[i].kind().clone(); - let item_path = - Path::new(self.tree_items[i].info().full_path_str()); + let item_kind = self.tree_items[i].kind().clone(); + let item_path = + Path::new(self.tree_items[i].info().full_path_str()); - if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed) - { - // we encountered an inner path that is still collapsed - inner_collapsed = Some(item_path.into()); - } + if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed) + { + // we encountered an inner path that is still collapsed + inner_collapsed = Some(item_path.into()); + } - if prefix - .as_ref() - .map_or(true, |prefix| item_path.starts_with(prefix)) - { - self.tree_items[i].info_mut().set_visible(true); - } else { - // if we do not set defaults we can early out - if set_defaults { - self.tree_items[i].info_mut().set_visible(false); - } else { - return; - } - } - } - } + if prefix + .as_ref() + .map_or(true, |prefix| item_path.starts_with(prefix)) + { + self.tree_items[i].info_mut().set_visible(true); + } else { + // if we do not set defaults we can early out + if set_defaults { + self.tree_items[i].info_mut().set_visible(false); + } else { + return; + } + } + } + } - fn fold_paths( - items: &mut Vec, - paths: &HashMap<&Path, usize>, - ) { - let mut i = 0; + fn fold_paths( + items: &mut Vec, + paths: &HashMap<&Path, usize>, + ) { + let mut i = 0; - while i < items.len() { - let item = &items[i]; - if item.kind().is_path() { - let children = paths - .get(&Path::new(item.info().full_path_str())); + while i < items.len() { + let item = &items[i]; + if item.kind().is_path() { + let children = paths + .get(&Path::new(item.info().full_path_str())); - if let Some(children) = children { - if *children == 1 { - if i + 1 >= items.len() { - return; - } + if let Some(children) = children { + if *children == 1 { + if i + 1 >= items.len() { + return; + } - if items - .get(i + 1) - .map(|item| item.kind().is_path()) - .unwrap_or_default() - { - let next_item = items.remove(i + 1); - let item_mut = &mut items[i]; - item_mut.fold(next_item); + if items + .get(i + 1) + .map(|item| item.kind().is_path()) + .unwrap_or_default() + { + let next_item = items.remove(i + 1); + let item_mut = &mut items[i]; + item_mut.fold(next_item); - let prefix = item_mut - .info() - .full_path_str() - .to_owned(); + let prefix = item_mut + .info() + .full_path_str() + .to_owned(); - Self::unindent(items, &prefix, i + 1); - continue; - } - } - } - } + Self::unindent(items, &prefix, i + 1); + continue; + } + } + } + } - i += 1; - } - } + i += 1; + } + } - fn unindent( - items: &mut Vec, - prefix: &str, - start: usize, - ) { - for elem in items.iter_mut().skip(start) { - if elem.info().full_path_str().starts_with(prefix) { - elem.info_mut().unindent(); - } else { - return; - } - } - } + fn unindent( + items: &mut Vec, + prefix: &str, + start: usize, + ) { + for elem in items.iter_mut().skip(start) { + if elem.info().full_path_str().starts_with(prefix) { + elem.info_mut().unindent(); + } else { + return; + } + } + } } #[cfg(test)] mod tests { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_simple() { - let items = vec![ - Path::new("file.txt"), // - ]; - - let res = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - assert!(res.tree_items[0].info().is_visible()); - assert_eq!(res.tree_items[0].info().indent(), 0); - assert_eq!(res.tree_items[0].info().path(), items[0]); - assert_eq!(res.tree_items[0].info().full_path(), items[0]); - - let items = vec![ - Path::new("file.txt"), // - Path::new("file2.txt"), // - ]; - - let res = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - assert_eq!(res.tree_items.len(), 2); - assert_eq!(res.tree_items.len(), res.len()); - assert_eq!(res.tree_items[1].info().path(), items[1]); - } - - #[test] - fn test_push_path() { - let mut items = Vec::new(); - let mut paths: HashMap<&Path, usize> = HashMap::new(); - - FileTreeItems::push_dirs( - Path::new("a/b/c"), - &mut items, - &mut paths, - &BTreeSet::new(), - ) - .unwrap(); - - assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); - - FileTreeItems::push_dirs( - Path::new("a/b2/c"), - &mut items, - &mut paths, - &BTreeSet::new(), - ) - .unwrap(); - - assert_eq!(*paths.get(&Path::new("a")).unwrap(), 2); - } - - #[test] - fn test_push_path2() { - let mut items = Vec::new(); - let mut paths: HashMap<&Path, usize> = HashMap::new(); - - FileTreeItems::push_dirs( - Path::new("a/b/c"), - &mut items, - &mut paths, - &BTreeSet::new(), - ) - .unwrap(); - - assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); - assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 1); - - FileTreeItems::push_dirs( - Path::new("a/b/d"), - &mut items, - &mut paths, - &BTreeSet::new(), - ) - .unwrap(); - - assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); - assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 2); - } - - #[test] - fn test_folder() { - let items = vec![ - Path::new("a/file.txt"), // - ]; - - let res = FileTreeItems::new(&items, &BTreeSet::new()) - .unwrap() - .tree_items - .iter() - .map(|i| i.info().full_path_str().to_string()) - .collect::>(); - - assert_eq!( - res, - vec![String::from("a"), String::from("a/file.txt"),] - ); - } - - #[test] - fn test_indent() { - let items = vec![ - Path::new("a/b/file.txt"), // - ]; - - let list = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut res = list - .tree_items - .iter() - .map(|i| (i.info().indent(), i.info().path())); - - assert_eq!(res.next(), Some((0, Path::new("a/b")))); - assert_eq!(res.next(), Some((1, Path::new("file.txt")))); - } - - #[test] - fn test_indent_folder_file_name() { - let items = vec![ - Path::new("a/b"), // - Path::new("a.txt"), // - ]; - - let list = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut res = list - .tree_items - .iter() - .map(|i| (i.info().indent(), i.info().path_str())); - - assert_eq!(res.next(), Some((0, "a"))); - assert_eq!(res.next(), Some((1, "b"))); - assert_eq!(res.next(), Some((0, "a.txt"))); - } - - #[test] - fn test_folder_dup() { - let items = vec![ - Path::new("a/file.txt"), // - Path::new("a/file2.txt"), // - ]; - - let tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - assert_eq!(tree.file_count(), 2); - assert_eq!(tree.len(), 3); - - let res = tree - .tree_items - .iter() - .map(|i| i.info().full_path_str().to_string()) - .collect::>(); - - assert_eq!( - res, - vec![ - String::from("a"), - String::from("a/file.txt"), - String::from("a/file2.txt"), - ] - ); - } - - #[test] - fn test_collapse() { - let items = vec![ - Path::new("a/file1.txt"), // - Path::new("b/file2.txt"), // - ]; - - let mut tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - assert!(tree.tree_items[1].info().is_visible()); - - tree.collapse(0, false); - - assert!(!tree.tree_items[1].info().is_visible()); - } - - #[test] - fn test_iterate_collapsed() { - let items = vec![ - Path::new("a/file1.txt"), // - Path::new("b/file2.txt"), // - ]; - - let mut tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - tree.collapse(0, false); - - let mut it = tree.iterate(0, 10); - - assert_eq!(it.next().unwrap().0, 0); - assert_eq!(it.next().unwrap().0, 2); - assert_eq!(it.next().unwrap().0, 3); - assert_eq!(it.next(), None); - } - - pub fn get_visibles(tree: &FileTreeItems) -> Vec { - tree.tree_items - .iter() - .map(|e| e.info().is_visible()) - .collect::>() - } - - #[test] - fn test_expand() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; - - //0 a/ - //1 b/ - //2 c - //3 d - - let mut tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - tree.collapse(1, false); - - let visibles = get_visibles(&tree); - - assert_eq!( - visibles, - vec![ - true, // - true, // - false, // - true, - ] - ); - - tree.expand(1, false); - - let visibles = get_visibles(&tree); - - assert_eq!( - visibles, - vec![ - true, // - true, // - true, // - true, - ] - ); - } - - #[test] - fn test_expand_bug() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/b2/d"), // - ]; - - //0 a/ - //1 b/ - //2 c - //3 b2/ - //4 d - - let mut tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - tree.collapse(1, false); - tree.collapse(0, false); - - assert_eq!( - get_visibles(&tree), - vec![ - true, // - false, // - false, // - false, // - false, - ] - ); - - tree.expand(0, false); - - assert_eq!( - get_visibles(&tree), - vec![ - true, // - true, // - false, // - true, // - true, - ] - ); - } - - #[test] - fn test_collapse_too_much() { - let items = vec![ - Path::new("a/b"), // - Path::new("a2/c"), // - ]; - - //0 a/ - //1 b - //2 a2/ - //3 c - - let mut tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - tree.collapse(0, false); - - let visibles = get_visibles(&tree); - - assert_eq!( - visibles, - vec![ - true, // - false, // - true, // - true, - ] - ); - } - - #[test] - fn test_expand_with_collapsed_sub_parts() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/d"), // - ]; - - //0 a/ - //1 b/ - //2 c - //3 d - - let mut tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - tree.collapse(1, false); - - let visibles = get_visibles(&tree); - - assert_eq!( - visibles, - vec![ - true, // - true, // - false, // - true, - ] - ); - - tree.collapse(0, false); - - let visibles = get_visibles(&tree); - - assert_eq!( - visibles, - vec![ - true, // - false, // - false, // - false, - ] - ); - - tree.expand(0, false); - - let visibles = get_visibles(&tree); - - assert_eq!( - visibles, - vec![ - true, // - true, // - false, // - true, - ] - ); - } + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_simple() { + let items = vec![ + Path::new("file.txt"), // + ]; + + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + assert!(res.tree_items[0].info().is_visible()); + assert_eq!(res.tree_items[0].info().indent(), 0); + assert_eq!(res.tree_items[0].info().path(), items[0]); + assert_eq!(res.tree_items[0].info().full_path(), items[0]); + + let items = vec![ + Path::new("file.txt"), // + Path::new("file2.txt"), // + ]; + + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + assert_eq!(res.tree_items.len(), 2); + assert_eq!(res.tree_items.len(), res.len()); + assert_eq!(res.tree_items[1].info().path(), items[1]); + } + + #[test] + fn test_push_path() { + let mut items = Vec::new(); + let mut paths: HashMap<&Path, usize> = HashMap::new(); + + FileTreeItems::push_dirs( + Path::new("a/b/c"), + &mut items, + &mut paths, + &BTreeSet::new(), + ) + .unwrap(); + + assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); + + FileTreeItems::push_dirs( + Path::new("a/b2/c"), + &mut items, + &mut paths, + &BTreeSet::new(), + ) + .unwrap(); + + assert_eq!(*paths.get(&Path::new("a")).unwrap(), 2); + } + + #[test] + fn test_push_path2() { + let mut items = Vec::new(); + let mut paths: HashMap<&Path, usize> = HashMap::new(); + + FileTreeItems::push_dirs( + Path::new("a/b/c"), + &mut items, + &mut paths, + &BTreeSet::new(), + ) + .unwrap(); + + assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); + assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 1); + + FileTreeItems::push_dirs( + Path::new("a/b/d"), + &mut items, + &mut paths, + &BTreeSet::new(), + ) + .unwrap(); + + assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); + assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 2); + } + + #[test] + fn test_folder() { + let items = vec![ + Path::new("a/file.txt"), // + ]; + + let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() + .tree_items + .iter() + .map(|i| i.info().full_path_str().to_string()) + .collect::>(); + + assert_eq!( + res, + vec![String::from("a"), String::from("a/file.txt"),] + ); + } + + #[test] + fn test_indent() { + let items = vec![ + Path::new("a/b/file.txt"), // + ]; + + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let mut res = list + .tree_items + .iter() + .map(|i| (i.info().indent(), i.info().path())); + + assert_eq!(res.next(), Some((0, Path::new("a/b")))); + assert_eq!(res.next(), Some((1, Path::new("file.txt")))); + } + + #[test] + fn test_indent_folder_file_name() { + let items = vec![ + Path::new("a/b"), // + Path::new("a.txt"), // + ]; + + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let mut res = list + .tree_items + .iter() + .map(|i| (i.info().indent(), i.info().path_str())); + + assert_eq!(res.next(), Some((0, "a"))); + assert_eq!(res.next(), Some((1, "b"))); + assert_eq!(res.next(), Some((0, "a.txt"))); + } + + #[test] + fn test_folder_dup() { + let items = vec![ + Path::new("a/file.txt"), // + Path::new("a/file2.txt"), // + ]; + + let tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + assert_eq!(tree.file_count(), 2); + assert_eq!(tree.len(), 3); + + let res = tree + .tree_items + .iter() + .map(|i| i.info().full_path_str().to_string()) + .collect::>(); + + assert_eq!( + res, + vec![ + String::from("a"), + String::from("a/file.txt"), + String::from("a/file2.txt"), + ] + ); + } + + #[test] + fn test_collapse() { + let items = vec![ + Path::new("a/file1.txt"), // + Path::new("b/file2.txt"), // + ]; + + let mut tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + assert!(tree.tree_items[1].info().is_visible()); + + tree.collapse(0, false); + + assert!(!tree.tree_items[1].info().is_visible()); + } + + #[test] + fn test_iterate_collapsed() { + let items = vec![ + Path::new("a/file1.txt"), // + Path::new("b/file2.txt"), // + ]; + + let mut tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + tree.collapse(0, false); + + let mut it = tree.iterate(0, 10); + + assert_eq!(it.next().unwrap().0, 0); + assert_eq!(it.next().unwrap().0, 2); + assert_eq!(it.next().unwrap().0, 3); + assert_eq!(it.next(), None); + } + + pub fn get_visibles(tree: &FileTreeItems) -> Vec { + tree.tree_items + .iter() + .map(|e| e.info().is_visible()) + .collect::>() + } + + #[test] + fn test_expand() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; + + //0 a/ + //1 b/ + //2 c + //3 d + + let mut tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + tree.collapse(1, false); + + let visibles = get_visibles(&tree); + + assert_eq!( + visibles, + vec![ + true, // + true, // + false, // + true, + ] + ); + + tree.expand(1, false); + + let visibles = get_visibles(&tree); + + assert_eq!( + visibles, + vec![ + true, // + true, // + true, // + true, + ] + ); + } + + #[test] + fn test_expand_bug() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/b2/d"), // + ]; + + //0 a/ + //1 b/ + //2 c + //3 b2/ + //4 d + + let mut tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + tree.collapse(1, false); + tree.collapse(0, false); + + assert_eq!( + get_visibles(&tree), + vec![ + true, // + false, // + false, // + false, // + false, + ] + ); + + tree.expand(0, false); + + assert_eq!( + get_visibles(&tree), + vec![ + true, // + true, // + false, // + true, // + true, + ] + ); + } + + #[test] + fn test_collapse_too_much() { + let items = vec![ + Path::new("a/b"), // + Path::new("a2/c"), // + ]; + + //0 a/ + //1 b + //2 a2/ + //3 c + + let mut tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + tree.collapse(0, false); + + let visibles = get_visibles(&tree); + + assert_eq!( + visibles, + vec![ + true, // + false, // + true, // + true, + ] + ); + } + + #[test] + fn test_expand_with_collapsed_sub_parts() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/d"), // + ]; + + //0 a/ + //1 b/ + //2 c + //3 d + + let mut tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + tree.collapse(1, false); + + let visibles = get_visibles(&tree); + + assert_eq!( + visibles, + vec![ + true, // + true, // + false, // + true, + ] + ); + + tree.collapse(0, false); + + let visibles = get_visibles(&tree); + + assert_eq!( + visibles, + vec![ + true, // + false, // + false, // + false, + ] + ); + + tree.expand(0, false); + + let visibles = get_visibles(&tree); + + assert_eq!( + visibles, + vec![ + true, // + true, // + false, // + true, + ] + ); + } } #[cfg(test)] mod test_merging { - use super::*; - use pretty_assertions::assert_eq; + use super::*; + use pretty_assertions::assert_eq; - #[test] - fn test_merge_simple() { - let list = vec![Path::new("a/b/c")]; - let (mut items, paths) = - FileTreeItems::create_items(&list, &BTreeSet::new()) - .unwrap(); + #[test] + fn test_merge_simple() { + let list = vec![Path::new("a/b/c")]; + let (mut items, paths) = + FileTreeItems::create_items(&list, &BTreeSet::new()) + .unwrap(); - assert_eq!(items.len(), 3); + assert_eq!(items.len(), 3); - FileTreeItems::fold_paths(&mut items, &paths); + FileTreeItems::fold_paths(&mut items, &paths); - assert_eq!(items.len(), 2); - } + assert_eq!(items.len(), 2); + } - #[test] - fn test_merge_simple2() { - let list = vec![ - Path::new("a/b/c"), // - Path::new("a/b/d"), // - ]; - let (mut items, paths) = - FileTreeItems::create_items(&list, &BTreeSet::new()) - .unwrap(); + #[test] + fn test_merge_simple2() { + let list = vec![ + Path::new("a/b/c"), // + Path::new("a/b/d"), // + ]; + let (mut items, paths) = + FileTreeItems::create_items(&list, &BTreeSet::new()) + .unwrap(); - assert_eq!(paths.len(), 2); - assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); - assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 2); - assert_eq!(items.len(), 4); + assert_eq!(paths.len(), 2); + assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1); + assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 2); + assert_eq!(items.len(), 4); - FileTreeItems::fold_paths(&mut items, &paths); + FileTreeItems::fold_paths(&mut items, &paths); - assert_eq!(items.len(), 3); - } + assert_eq!(items.len(), 3); + } - #[test] - fn test_merge_indent() { - let list = vec![ - Path::new("a/b/c/d"), // - Path::new("a/e/f"), // - ]; + #[test] + fn test_merge_indent() { + let list = vec![ + Path::new("a/b/c/d"), // + Path::new("a/e/f"), // + ]; - //0:0 a/ - //1:1 b/c - //2:2 d - //3:1 e/ - //4:2 f + //0:0 a/ + //1:1 b/c + //2:2 d + //3:1 e/ + //4:2 f - let (mut items, paths) = - FileTreeItems::create_items(&list, &BTreeSet::new()) - .unwrap(); + let (mut items, paths) = + FileTreeItems::create_items(&list, &BTreeSet::new()) + .unwrap(); - assert_eq!(items.len(), 6); + assert_eq!(items.len(), 6); - assert_eq!(paths.len(), 4); - assert_eq!(*paths.get(&Path::new("a")).unwrap(), 2); - assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 1); - assert_eq!(*paths.get(&Path::new("a/b/c")).unwrap(), 1); - assert_eq!(*paths.get(&Path::new("a/e")).unwrap(), 1); + assert_eq!(paths.len(), 4); + assert_eq!(*paths.get(&Path::new("a")).unwrap(), 2); + assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 1); + assert_eq!(*paths.get(&Path::new("a/b/c")).unwrap(), 1); + assert_eq!(*paths.get(&Path::new("a/e")).unwrap(), 1); - FileTreeItems::fold_paths(&mut items, &paths); + FileTreeItems::fold_paths(&mut items, &paths); - let indents: Vec = - items.iter().map(|i| i.info().indent()).collect(); - assert_eq!(indents, vec![0, 1, 2, 1, 2]); - } + let indents: Vec = + items.iter().map(|i| i.info().indent()).collect(); + assert_eq!(indents, vec![0, 1, 2, 1, 2]); + } - #[test] - fn test_merge_single_paths() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/b/d"), // - ]; + #[test] + fn test_merge_single_paths() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/b/d"), // + ]; - //0 a/b/ - //1 c - //2 d + //0 a/b/ + //1 c + //2 d - let tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut it = tree - .iterate(0, 10) - .map(|(_, item)| item.info().full_path_str()); + let mut it = tree + .iterate(0, 10) + .map(|(_, item)| item.info().full_path_str()); - assert_eq!(it.next().unwrap(), "a/b"); - assert_eq!(it.next().unwrap(), "a/b/c"); - assert_eq!(it.next().unwrap(), "a/b/d"); - assert_eq!(it.next(), None); - } + assert_eq!(it.next().unwrap(), "a/b"); + assert_eq!(it.next().unwrap(), "a/b/c"); + assert_eq!(it.next().unwrap(), "a/b/d"); + assert_eq!(it.next(), None); + } - #[test] - fn test_merge_nothing() { - let items = vec![ - Path::new("a/b/c"), // - Path::new("a/b2/d"), // - ]; + #[test] + fn test_merge_nothing() { + let items = vec![ + Path::new("a/b/c"), // + Path::new("a/b2/d"), // + ]; - //0 a/ - //1 b/ - //2 c - //3 b2/ - //4 d + //0 a/ + //1 b/ + //2 c + //3 b2/ + //4 d - let tree = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let tree = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut it = tree - .iterate(0, 10) - .map(|(_, item)| item.info().full_path_str()); + let mut it = tree + .iterate(0, 10) + .map(|(_, item)| item.info().full_path_str()); - assert_eq!(it.next().unwrap(), "a"); - assert_eq!(it.next().unwrap(), "a/b"); - assert_eq!(it.next().unwrap(), "a/b/c"); - assert_eq!(it.next().unwrap(), "a/b2"); - assert_eq!(it.next().unwrap(), "a/b2/d"); - assert_eq!(it.next(), None); - } + assert_eq!(it.next().unwrap(), "a"); + assert_eq!(it.next().unwrap(), "a/b"); + assert_eq!(it.next().unwrap(), "a/b/c"); + assert_eq!(it.next().unwrap(), "a/b2"); + assert_eq!(it.next().unwrap(), "a/b2/d"); + assert_eq!(it.next(), None); + } } diff --git a/filetreelist/src/item.rs b/filetreelist/src/item.rs index 8c9b6d16..f343e810 100644 --- a/filetreelist/src/item.rs +++ b/filetreelist/src/item.rs @@ -1,85 +1,85 @@ use crate::error::Result; use std::{ - convert::TryFrom, - path::{Path, PathBuf}, + convert::TryFrom, + path::{Path, PathBuf}, }; /// holds the information shared among all `FileTreeItem` in a `FileTree` #[derive(Debug, Clone)] pub struct TreeItemInfo { - /// indent level - indent: u8, - /// currently visible depending on the folder collapse states - visible: bool, - /// contains this paths last component and folded up paths added to it - /// if this is `None` nothing was folding into here - folded: Option, - /// the full path - full_path: PathBuf, + /// indent level + indent: u8, + /// currently visible depending on the folder collapse states + visible: bool, + /// contains this paths last component and folded up paths added to it + /// if this is `None` nothing was folding into here + folded: Option, + /// the full path + full_path: PathBuf, } impl TreeItemInfo { - /// - pub const fn new(indent: u8, full_path: PathBuf) -> Self { - Self { - indent, - visible: true, - folded: None, - full_path, - } - } + /// + pub const fn new(indent: u8, full_path: PathBuf) -> Self { + Self { + indent, + visible: true, + folded: None, + full_path, + } + } - /// - pub const fn is_visible(&self) -> bool { - self.visible - } + /// + pub const fn is_visible(&self) -> bool { + self.visible + } - /// - //TODO: remove - pub fn full_path_str(&self) -> &str { - self.full_path.to_str().unwrap_or_default() - } + /// + //TODO: remove + pub fn full_path_str(&self) -> &str { + self.full_path.to_str().unwrap_or_default() + } - /// - pub fn full_path(&self) -> &Path { - self.full_path.as_path() - } + /// + pub fn full_path(&self) -> &Path { + self.full_path.as_path() + } - /// like `path` but as `&str` - pub fn path_str(&self) -> &str { - self.path().as_os_str().to_str().unwrap_or_default() - } + /// like `path` but as `&str` + pub fn path_str(&self) -> &str { + self.path().as_os_str().to_str().unwrap_or_default() + } - /// returns the last component of `full_path` - /// or the last components plus folded up children paths - pub fn path(&self) -> &Path { - self.folded.as_ref().map_or_else( - || { - Path::new( - self.full_path - .components() - .last() - .and_then(|c| c.as_os_str().to_str()) - .unwrap_or_default(), - ) - }, - |folding| folding.as_path(), - ) - } + /// returns the last component of `full_path` + /// or the last components plus folded up children paths + pub fn path(&self) -> &Path { + self.folded.as_ref().map_or_else( + || { + Path::new( + self.full_path + .components() + .last() + .and_then(|c| c.as_os_str().to_str()) + .unwrap_or_default(), + ) + }, + |folding| folding.as_path(), + ) + } - /// - pub const fn indent(&self) -> u8 { - self.indent - } + /// + pub const fn indent(&self) -> u8 { + self.indent + } - /// - pub fn unindent(&mut self) { - self.indent = self.indent.saturating_sub(1); - } + /// + pub fn unindent(&mut self) { + self.indent = self.indent.saturating_sub(1); + } - pub fn set_visible(&mut self, visible: bool) { - self.visible = visible; - } + pub fn set_visible(&mut self, visible: bool) { + self.visible = visible; + } } /// attribute used to indicate the collapse/expand state of a path item @@ -89,142 +89,142 @@ pub struct PathCollapsed(pub bool); /// `FileTreeItem` can be of two kinds #[derive(PartialEq, Debug, Clone)] pub enum FileTreeItemKind { - Path(PathCollapsed), - File, + Path(PathCollapsed), + File, } impl FileTreeItemKind { - pub const fn is_path(&self) -> bool { - matches!(self, Self::Path(_)) - } + pub const fn is_path(&self) -> bool { + matches!(self, Self::Path(_)) + } - pub const fn is_path_collapsed(&self) -> bool { - match self { - Self::Path(collapsed) => collapsed.0, - Self::File => false, - } - } + pub const fn is_path_collapsed(&self) -> bool { + match self { + Self::Path(collapsed) => collapsed.0, + Self::File => false, + } + } } /// `FileTreeItem` can be of two kinds: see `FileTreeItem` but shares an info #[derive(Debug, Clone)] pub struct FileTreeItem { - info: TreeItemInfo, - kind: FileTreeItemKind, + info: TreeItemInfo, + kind: FileTreeItemKind, } impl FileTreeItem { - pub fn new_file(path: &Path) -> Result { - let item_path = PathBuf::from(path); + pub fn new_file(path: &Path) -> Result { + let item_path = PathBuf::from(path); - let indent = u8::try_from( - item_path.ancestors().count().saturating_sub(2), - )?; + let indent = u8::try_from( + item_path.ancestors().count().saturating_sub(2), + )?; - Ok(Self { - info: TreeItemInfo::new(indent, item_path), - kind: FileTreeItemKind::File, - }) - } + Ok(Self { + info: TreeItemInfo::new(indent, item_path), + kind: FileTreeItemKind::File, + }) + } - pub fn new_path(path: &Path, collapsed: bool) -> Result { - let indent = - u8::try_from(path.ancestors().count().saturating_sub(2))?; + pub fn new_path(path: &Path, collapsed: bool) -> Result { + let indent = + u8::try_from(path.ancestors().count().saturating_sub(2))?; - Ok(Self { - info: TreeItemInfo::new(indent, path.to_owned()), - kind: FileTreeItemKind::Path(PathCollapsed(collapsed)), - }) - } + Ok(Self { + info: TreeItemInfo::new(indent, path.to_owned()), + kind: FileTreeItemKind::Path(PathCollapsed(collapsed)), + }) + } - /// - pub fn fold(&mut self, next: Self) { - if let Some(folded) = self.info.folded.as_mut() { - *folded = folded.join(next.info.path()); - } else { - self.info.folded = - Some(self.info.path().join(next.info.path())); - } + /// + pub fn fold(&mut self, next: Self) { + if let Some(folded) = self.info.folded.as_mut() { + *folded = folded.join(next.info.path()); + } else { + self.info.folded = + Some(self.info.path().join(next.info.path())); + } - self.info.full_path = next.info.full_path; - } + self.info.full_path = next.info.full_path; + } - /// - pub const fn info(&self) -> &TreeItemInfo { - &self.info - } + /// + pub const fn info(&self) -> &TreeItemInfo { + &self.info + } - /// - pub fn info_mut(&mut self) -> &mut TreeItemInfo { - &mut self.info - } + /// + pub fn info_mut(&mut self) -> &mut TreeItemInfo { + &mut self.info + } - /// - pub const fn kind(&self) -> &FileTreeItemKind { - &self.kind - } + /// + pub const fn kind(&self) -> &FileTreeItemKind { + &self.kind + } - /// - pub fn collapse_path(&mut self) { - self.kind = FileTreeItemKind::Path(PathCollapsed(true)); - } + /// + pub fn collapse_path(&mut self) { + self.kind = FileTreeItemKind::Path(PathCollapsed(true)); + } - /// - pub fn expand_path(&mut self) { - self.kind = FileTreeItemKind::Path(PathCollapsed(false)); - } + /// + pub fn expand_path(&mut self) { + self.kind = FileTreeItemKind::Path(PathCollapsed(false)); + } - /// - pub fn hide(&mut self) { - self.info.visible = false; - } + /// + pub fn hide(&mut self) { + self.info.visible = false; + } } impl Eq for FileTreeItem {} impl PartialEq for FileTreeItem { - fn eq(&self, other: &Self) -> bool { - self.info.full_path.eq(&other.info.full_path) - } + fn eq(&self, other: &Self) -> bool { + self.info.full_path.eq(&other.info.full_path) + } } impl PartialOrd for FileTreeItem { - fn partial_cmp( - &self, - other: &Self, - ) -> Option { - self.info.full_path.partial_cmp(&other.info.full_path) - } + fn partial_cmp( + &self, + other: &Self, + ) -> Option { + self.info.full_path.partial_cmp(&other.info.full_path) + } } impl Ord for FileTreeItem { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.info.path().cmp(other.info.path()) - } + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.info.path().cmp(other.info.path()) + } } #[cfg(test)] mod tests { - use super::*; - use pretty_assertions::assert_eq; + use super::*; + use pretty_assertions::assert_eq; - #[test] - fn test_smoke() { - let mut a = - FileTreeItem::new_path(Path::new("a"), false).unwrap(); + #[test] + fn test_smoke() { + let mut a = + FileTreeItem::new_path(Path::new("a"), false).unwrap(); - assert_eq!(a.info.full_path_str(), "a"); - assert_eq!(a.info.path_str(), "a"); + assert_eq!(a.info.full_path_str(), "a"); + assert_eq!(a.info.path_str(), "a"); - let b = - FileTreeItem::new_path(Path::new("a/b"), false).unwrap(); - a.fold(b); + let b = + FileTreeItem::new_path(Path::new("a/b"), false).unwrap(); + a.fold(b); - assert_eq!(a.info.full_path_str(), "a/b"); - assert_eq!( - &a.info.folded.as_ref().unwrap(), - &Path::new("a/b") - ); - assert_eq!(a.info.path(), Path::new("a/b")); - } + assert_eq!(a.info.full_path_str(), "a/b"); + assert_eq!( + &a.info.folded.as_ref().unwrap(), + &Path::new("a/b") + ); + assert_eq!(a.info.path(), Path::new("a/b")); + } } diff --git a/filetreelist/src/lib.rs b/filetreelist/src/lib.rs index 08955782..8a72deb1 100644 --- a/filetreelist/src/lib.rs +++ b/filetreelist/src/lib.rs @@ -1,11 +1,11 @@ // #![forbid(missing_docs)] #![forbid(unsafe_code)] #![deny( - unused_imports, - unused_must_use, - dead_code, - unstable_name_collisions, - unused_assignments + unused_imports, + unused_must_use, + dead_code, + unstable_name_collisions, + unused_assignments )] #![deny(clippy::all, clippy::perf, clippy::nursery, clippy::pedantic)] #![deny(clippy::expect_used)] @@ -27,7 +27,7 @@ mod tree_iter; mod treeitems_iter; pub use crate::{ - filetree::FileTree, - filetree::MoveSelection, - item::{FileTreeItem, TreeItemInfo}, + filetree::FileTree, + filetree::MoveSelection, + item::{FileTreeItem, TreeItemInfo}, }; diff --git a/filetreelist/src/tree_iter.rs b/filetreelist/src/tree_iter.rs index b5e331a1..8f6afe2c 100644 --- a/filetreelist/src/tree_iter.rs +++ b/filetreelist/src/tree_iter.rs @@ -1,33 +1,33 @@ use crate::{item::FileTreeItem, treeitems_iter::TreeItemsIterator}; pub struct TreeIterator<'a> { - item_iter: TreeItemsIterator<'a>, - selection: Option, + item_iter: TreeItemsIterator<'a>, + selection: Option, } impl<'a> TreeIterator<'a> { - pub const fn new( - item_iter: TreeItemsIterator<'a>, - selection: Option, - ) -> Self { - Self { - item_iter, - selection, - } - } + pub const fn new( + item_iter: TreeItemsIterator<'a>, + selection: Option, + ) -> Self { + Self { + item_iter, + selection, + } + } } impl<'a> Iterator for TreeIterator<'a> { - type Item = (&'a FileTreeItem, bool); + type Item = (&'a FileTreeItem, bool); - fn next(&mut self) -> Option { - self.item_iter.next().map(|(index, item)| { - ( - item, - self.selection - .map(|i| i == index) - .unwrap_or_default(), - ) - }) - } + fn next(&mut self) -> Option { + self.item_iter.next().map(|(index, item)| { + ( + item, + self.selection + .map(|i| i == index) + .unwrap_or_default(), + ) + }) + } } diff --git a/filetreelist/src/treeitems_iter.rs b/filetreelist/src/treeitems_iter.rs index 7ca187a1..4feeb3e8 100644 --- a/filetreelist/src/treeitems_iter.rs +++ b/filetreelist/src/treeitems_iter.rs @@ -1,60 +1,60 @@ use crate::{filetreeitems::FileTreeItems, item::FileTreeItem}; pub struct TreeItemsIterator<'a> { - tree: &'a FileTreeItems, - index: usize, - increments: Option, - max_amount: usize, + tree: &'a FileTreeItems, + index: usize, + increments: Option, + max_amount: usize, } impl<'a> TreeItemsIterator<'a> { - pub const fn new( - tree: &'a FileTreeItems, - start: usize, - max_amount: usize, - ) -> Self { - TreeItemsIterator { - max_amount, - increments: None, - index: start, - tree, - } - } + pub const fn new( + tree: &'a FileTreeItems, + start: usize, + max_amount: usize, + ) -> Self { + TreeItemsIterator { + max_amount, + increments: None, + index: start, + tree, + } + } } impl<'a> Iterator for TreeItemsIterator<'a> { - type Item = (usize, &'a FileTreeItem); + type Item = (usize, &'a FileTreeItem); - fn next(&mut self) -> Option { - if self.increments.unwrap_or_default() < self.max_amount { - let items = &self.tree.tree_items; + fn next(&mut self) -> Option { + if self.increments.unwrap_or_default() < self.max_amount { + let items = &self.tree.tree_items; - let mut init = self.increments.is_none(); + let mut init = self.increments.is_none(); - if let Some(i) = self.increments.as_mut() { - *i += 1; - } else { - self.increments = Some(0); - }; + if let Some(i) = self.increments.as_mut() { + *i += 1; + } else { + self.increments = Some(0); + }; - loop { - if !init { - self.index += 1; - } - init = false; + loop { + if !init { + self.index += 1; + } + init = false; - if self.index >= self.tree.len() { - break; - } + if self.index >= self.tree.len() { + break; + } - let elem = &items[self.index]; + let elem = &items[self.index]; - if elem.info().is_visible() { - return Some((self.index, &items[self.index])); - } - } - } + if elem.info().is_visible() { + return Some((self.index, &items[self.index])); + } + } + } - None - } + None + } } diff --git a/invalidstring/src/lib.rs b/invalidstring/src/lib.rs index be241417..9d245997 100644 --- a/invalidstring/src/lib.rs +++ b/invalidstring/src/lib.rs @@ -1,8 +1,8 @@ /// uses unsafe to postfix the string with invalid utf8 data pub fn invalid_utf8(prefix: &str) -> String { - let bytes = b"\xc3\x73"; + let bytes = b"\xc3\x73"; - unsafe { - format!("{}{}", prefix, std::str::from_utf8_unchecked(bytes)) - } + unsafe { + format!("{}{}", prefix, std::str::from_utf8_unchecked(bytes)) + } } diff --git a/rustfmt.toml b/rustfmt.toml index 81666c78..aec36615 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,3 @@ max_width=70 -hard_tabs=false +hard_tabs=true newline_style="Unix" \ No newline at end of file diff --git a/scopetime/src/lib.rs b/scopetime/src/lib.rs index 24fb13ef..0cf774b5 100644 --- a/scopetime/src/lib.rs +++ b/scopetime/src/lib.rs @@ -10,63 +10,63 @@ use std::time::Instant; /// pub struct ScopeTimeLog<'a> { - title: &'a str, - mod_path: &'a str, - file: &'a str, - line: u32, - time: Instant, + title: &'a str, + mod_path: &'a str, + file: &'a str, + line: u32, + time: Instant, } /// impl<'a> ScopeTimeLog<'a> { - /// - pub fn new( - mod_path: &'a str, - title: &'a str, - file: &'a str, - line: u32, - ) -> Self { - Self { - title, - mod_path, - file, - line, - time: Instant::now(), - } - } + /// + pub fn new( + mod_path: &'a str, + title: &'a str, + file: &'a str, + line: u32, + ) -> Self { + Self { + title, + mod_path, + file, + line, + time: Instant::now(), + } + } } impl<'a> Drop for ScopeTimeLog<'a> { - fn drop(&mut self) { - log::trace!( - "scopetime: {:?} ms [{}::{}] @{}:{}", - self.time.elapsed().as_millis(), - self.mod_path, - self.title, - self.file, - self.line, - ); - } + fn drop(&mut self) { + log::trace!( + "scopetime: {:?} ms [{}::{}] @{}:{}", + self.time.elapsed().as_millis(), + self.mod_path, + self.title, + self.file, + self.line, + ); + } } /// #[cfg(feature = "enabled")] #[macro_export] macro_rules! scope_time { - ($target:literal) => { - #[allow(unused_variables)] - let time = $crate::ScopeTimeLog::new( - module_path!(), - $target, - file!(), - line!(), - ); - }; + ($target:literal) => { + #[allow(unused_variables)] + let time = $crate::ScopeTimeLog::new( + module_path!(), + $target, + file!(), + line!(), + ); + }; } #[doc(hidden)] #[cfg(not(feature = "enabled"))] #[macro_export] macro_rules! scope_time { - ($target:literal) => {}; + ($target:literal) => {}; } diff --git a/src/app.rs b/src/app.rs index 000fbf0f..e58c889f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,829 +1,829 @@ use crate::{ - accessors, - cmdbar::CommandBar, - components::{ - event_pump, BlameFileComponent, BranchListComponent, - CommandBlocking, CommandInfo, CommitComponent, Component, - CreateBranchComponent, DrawableComponent, - ExternalEditorComponent, HelpComponent, - InspectCommitComponent, MsgComponent, PullComponent, - PushComponent, PushTagsComponent, RenameBranchComponent, - ResetComponent, RevisionFilesPopup, StashMsgComponent, - TagCommitComponent, TagListComponent, - }, - input::{Input, InputEvent, InputState}, - keys::{KeyConfig, SharedKeyConfig}, - queue::{Action, InternalEvent, NeedsUpdate, Queue}, - setup_popups, - strings::{self, order}, - tabs::{FilesTab, Revlog, StashList, Stashing, Status}, - ui::style::{SharedTheme, Theme}, - AsyncAppNotification, AsyncNotification, + accessors, + cmdbar::CommandBar, + components::{ + event_pump, BlameFileComponent, BranchListComponent, + CommandBlocking, CommandInfo, CommitComponent, Component, + CreateBranchComponent, DrawableComponent, + ExternalEditorComponent, HelpComponent, + InspectCommitComponent, MsgComponent, PullComponent, + PushComponent, PushTagsComponent, RenameBranchComponent, + ResetComponent, RevisionFilesPopup, StashMsgComponent, + TagCommitComponent, TagListComponent, + }, + input::{Input, InputEvent, InputState}, + keys::{KeyConfig, SharedKeyConfig}, + queue::{Action, InternalEvent, NeedsUpdate, Queue}, + setup_popups, + strings::{self, order}, + tabs::{FilesTab, Revlog, StashList, Stashing, Status}, + ui::style::{SharedTheme, Theme}, + AsyncAppNotification, AsyncNotification, }; use anyhow::{bail, Result}; use asyncgit::{sync, AsyncGitNotification, CWD}; use crossbeam_channel::Sender; use crossterm::event::{Event, KeyEvent}; use std::{ - cell::{Cell, RefCell}, - path::Path, - rc::Rc, + cell::{Cell, RefCell}, + path::Path, + rc::Rc, }; use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Margin, Rect}, - text::{Span, Spans}, - widgets::{Block, Borders, Tabs}, - Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Margin, Rect}, + text::{Span, Spans}, + widgets::{Block, Borders, Tabs}, + Frame, }; /// the main app type pub struct App { - do_quit: bool, - help: HelpComponent, - msg: MsgComponent, - reset: ResetComponent, - commit: CommitComponent, - blame_file_popup: BlameFileComponent, - stashmsg_popup: StashMsgComponent, - inspect_commit_popup: InspectCommitComponent, - external_editor_popup: ExternalEditorComponent, - revision_files_popup: RevisionFilesPopup, - push_popup: PushComponent, - push_tags_popup: PushTagsComponent, - pull_popup: PullComponent, - tag_commit_popup: TagCommitComponent, - create_branch_popup: CreateBranchComponent, - rename_branch_popup: RenameBranchComponent, - select_branch_popup: BranchListComponent, - tags_popup: TagListComponent, - cmdbar: RefCell, - tab: usize, - revlog: Revlog, - status_tab: Status, - stashing_tab: Stashing, - stashlist_tab: StashList, - files_tab: FilesTab, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - input: Input, + do_quit: bool, + help: HelpComponent, + msg: MsgComponent, + reset: ResetComponent, + commit: CommitComponent, + blame_file_popup: BlameFileComponent, + stashmsg_popup: StashMsgComponent, + inspect_commit_popup: InspectCommitComponent, + external_editor_popup: ExternalEditorComponent, + revision_files_popup: RevisionFilesPopup, + push_popup: PushComponent, + push_tags_popup: PushTagsComponent, + pull_popup: PullComponent, + tag_commit_popup: TagCommitComponent, + create_branch_popup: CreateBranchComponent, + rename_branch_popup: RenameBranchComponent, + select_branch_popup: BranchListComponent, + tags_popup: TagListComponent, + cmdbar: RefCell, + tab: usize, + revlog: Revlog, + status_tab: Status, + stashing_tab: Stashing, + stashlist_tab: StashList, + files_tab: FilesTab, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + input: Input, - // "Flags" - requires_redraw: Cell, - file_to_open: Option, + // "Flags" + requires_redraw: Cell, + file_to_open: Option, } // public interface impl App { - /// - #[allow(clippy::too_many_lines)] - pub fn new( - sender: &Sender, - sender_app: &Sender, - input: Input, - theme: Theme, - key_config: KeyConfig, - ) -> Self { - let queue = Queue::new(); - let theme = Rc::new(theme); - let key_config = Rc::new(key_config); + /// + #[allow(clippy::too_many_lines)] + pub fn new( + sender: &Sender, + sender_app: &Sender, + input: Input, + theme: Theme, + key_config: KeyConfig, + ) -> Self { + let queue = Queue::new(); + let theme = Rc::new(theme); + let key_config = Rc::new(key_config); - Self { - input, - reset: ResetComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - commit: CommitComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - blame_file_popup: BlameFileComponent::new( - &queue, - sender, - &strings::blame_title(&key_config), - theme.clone(), - key_config.clone(), - ), - revision_files_popup: RevisionFilesPopup::new( - &queue, - sender_app, - theme.clone(), - key_config.clone(), - ), - stashmsg_popup: StashMsgComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - inspect_commit_popup: InspectCommitComponent::new( - &queue, - sender, - theme.clone(), - key_config.clone(), - ), - external_editor_popup: ExternalEditorComponent::new( - theme.clone(), - key_config.clone(), - ), - push_popup: PushComponent::new( - &queue, - sender, - theme.clone(), - key_config.clone(), - ), - push_tags_popup: PushTagsComponent::new( - &queue, - sender, - theme.clone(), - key_config.clone(), - ), - pull_popup: PullComponent::new( - &queue, - sender, - theme.clone(), - key_config.clone(), - ), - tag_commit_popup: TagCommitComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - create_branch_popup: CreateBranchComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - rename_branch_popup: RenameBranchComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - select_branch_popup: BranchListComponent::new( - queue.clone(), - theme.clone(), - key_config.clone(), - ), - tags_popup: TagListComponent::new( - &queue, - sender_app, - theme.clone(), - key_config.clone(), - ), - do_quit: false, - cmdbar: RefCell::new(CommandBar::new( - theme.clone(), - key_config.clone(), - )), - help: HelpComponent::new( - theme.clone(), - key_config.clone(), - ), - msg: MsgComponent::new(theme.clone(), key_config.clone()), - tab: 0, - revlog: Revlog::new( - &queue, - sender, - theme.clone(), - key_config.clone(), - ), - status_tab: Status::new( - &queue, - sender, - theme.clone(), - key_config.clone(), - ), - stashing_tab: Stashing::new( - sender, - &queue, - theme.clone(), - key_config.clone(), - ), - stashlist_tab: StashList::new( - &queue, - theme.clone(), - key_config.clone(), - ), - files_tab: FilesTab::new( - sender_app, - &queue, - theme.clone(), - key_config.clone(), - ), - queue, - theme, - key_config, - requires_redraw: Cell::new(false), - file_to_open: None, - } - } + Self { + input, + reset: ResetComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + commit: CommitComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + blame_file_popup: BlameFileComponent::new( + &queue, + sender, + &strings::blame_title(&key_config), + theme.clone(), + key_config.clone(), + ), + revision_files_popup: RevisionFilesPopup::new( + &queue, + sender_app, + theme.clone(), + key_config.clone(), + ), + stashmsg_popup: StashMsgComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + inspect_commit_popup: InspectCommitComponent::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), + external_editor_popup: ExternalEditorComponent::new( + theme.clone(), + key_config.clone(), + ), + push_popup: PushComponent::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), + push_tags_popup: PushTagsComponent::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), + pull_popup: PullComponent::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), + tag_commit_popup: TagCommitComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + create_branch_popup: CreateBranchComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + rename_branch_popup: RenameBranchComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + select_branch_popup: BranchListComponent::new( + queue.clone(), + theme.clone(), + key_config.clone(), + ), + tags_popup: TagListComponent::new( + &queue, + sender_app, + theme.clone(), + key_config.clone(), + ), + do_quit: false, + cmdbar: RefCell::new(CommandBar::new( + theme.clone(), + key_config.clone(), + )), + help: HelpComponent::new( + theme.clone(), + key_config.clone(), + ), + msg: MsgComponent::new(theme.clone(), key_config.clone()), + tab: 0, + revlog: Revlog::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), + status_tab: Status::new( + &queue, + sender, + theme.clone(), + key_config.clone(), + ), + stashing_tab: Stashing::new( + sender, + &queue, + theme.clone(), + key_config.clone(), + ), + stashlist_tab: StashList::new( + &queue, + theme.clone(), + key_config.clone(), + ), + files_tab: FilesTab::new( + sender_app, + &queue, + theme.clone(), + key_config.clone(), + ), + queue, + theme, + key_config, + requires_redraw: Cell::new(false), + file_to_open: None, + } + } - /// - pub fn draw(&self, f: &mut Frame) -> Result<()> { - let fsize = f.size(); + /// + pub fn draw(&self, f: &mut Frame) -> Result<()> { + let fsize = f.size(); - self.cmdbar.borrow_mut().refresh_width(fsize.width); + self.cmdbar.borrow_mut().refresh_width(fsize.width); - let chunks_main = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Length(2), - Constraint::Min(2), - Constraint::Length(self.cmdbar.borrow().height()), - ] - .as_ref(), - ) - .split(fsize); + let chunks_main = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Length(2), + Constraint::Min(2), + Constraint::Length(self.cmdbar.borrow().height()), + ] + .as_ref(), + ) + .split(fsize); - self.cmdbar.borrow().draw(f, chunks_main[2]); + self.cmdbar.borrow().draw(f, chunks_main[2]); - self.draw_tabs(f, chunks_main[0]); + self.draw_tabs(f, chunks_main[0]); - //TODO: macro because of generic draw call - match self.tab { - 0 => self.status_tab.draw(f, chunks_main[1])?, - 1 => self.revlog.draw(f, chunks_main[1])?, - 2 => self.files_tab.draw(f, chunks_main[1])?, - 3 => self.stashing_tab.draw(f, chunks_main[1])?, - 4 => self.stashlist_tab.draw(f, chunks_main[1])?, - _ => bail!("unknown tab"), - }; + //TODO: macro because of generic draw call + match self.tab { + 0 => self.status_tab.draw(f, chunks_main[1])?, + 1 => self.revlog.draw(f, chunks_main[1])?, + 2 => self.files_tab.draw(f, chunks_main[1])?, + 3 => self.stashing_tab.draw(f, chunks_main[1])?, + 4 => self.stashlist_tab.draw(f, chunks_main[1])?, + _ => bail!("unknown tab"), + }; - self.draw_popups(f)?; + self.draw_popups(f)?; - Ok(()) - } + Ok(()) + } - /// - pub fn event(&mut self, ev: InputEvent) -> Result<()> { - log::trace!("event: {:?}", ev); + /// + pub fn event(&mut self, ev: InputEvent) -> Result<()> { + log::trace!("event: {:?}", ev); - if let InputEvent::Input(ev) = ev { - if self.check_hard_exit(ev) || self.check_quit(ev) { - return Ok(()); - } + if let InputEvent::Input(ev) = ev { + if self.check_hard_exit(ev) || self.check_quit(ev) { + return Ok(()); + } - let mut flags = NeedsUpdate::empty(); + let mut flags = NeedsUpdate::empty(); - if event_pump(ev, self.components_mut().as_mut_slice())? - .is_consumed() - { - flags.insert(NeedsUpdate::COMMANDS); - } else if let Event::Key(k) = ev { - let new_flags = if k == self.key_config.tab_toggle { - self.toggle_tabs(false)?; - NeedsUpdate::COMMANDS - } else if k == self.key_config.tab_toggle_reverse { - self.toggle_tabs(true)?; - NeedsUpdate::COMMANDS - } else if k == self.key_config.tab_status - || k == self.key_config.tab_log - || k == self.key_config.tab_files - || k == self.key_config.tab_stashing - || k == self.key_config.tab_stashes - { - self.switch_tab(k)?; - NeedsUpdate::COMMANDS - } else if k == self.key_config.cmd_bar_toggle { - self.cmdbar.borrow_mut().toggle_more(); - NeedsUpdate::empty() - } else { - NeedsUpdate::empty() - }; + if event_pump(ev, self.components_mut().as_mut_slice())? + .is_consumed() + { + flags.insert(NeedsUpdate::COMMANDS); + } else if let Event::Key(k) = ev { + let new_flags = if k == self.key_config.tab_toggle { + self.toggle_tabs(false)?; + NeedsUpdate::COMMANDS + } else if k == self.key_config.tab_toggle_reverse { + self.toggle_tabs(true)?; + NeedsUpdate::COMMANDS + } else if k == self.key_config.tab_status + || k == self.key_config.tab_log + || k == self.key_config.tab_files + || k == self.key_config.tab_stashing + || k == self.key_config.tab_stashes + { + self.switch_tab(k)?; + NeedsUpdate::COMMANDS + } else if k == self.key_config.cmd_bar_toggle { + self.cmdbar.borrow_mut().toggle_more(); + NeedsUpdate::empty() + } else { + NeedsUpdate::empty() + }; - flags.insert(new_flags); - } + flags.insert(new_flags); + } - self.process_queue(flags)?; - } else if let InputEvent::State(polling_state) = ev { - self.external_editor_popup.hide(); - if let InputState::Paused = polling_state { - let result = match self.file_to_open.take() { - Some(path) => { - ExternalEditorComponent::open_file_in_editor( - Path::new(&path), - ) - } - None => self.commit.show_editor(), - }; + self.process_queue(flags)?; + } else if let InputEvent::State(polling_state) = ev { + self.external_editor_popup.hide(); + if let InputState::Paused = polling_state { + let result = match self.file_to_open.take() { + Some(path) => { + ExternalEditorComponent::open_file_in_editor( + Path::new(&path), + ) + } + None => self.commit.show_editor(), + }; - if let Err(e) = result { - let msg = - format!("failed to launch editor:\n{}", e); - log::error!("{}", msg.as_str()); - self.msg.show_error(msg.as_str())?; - } + if let Err(e) = result { + let msg = + format!("failed to launch editor:\n{}", e); + log::error!("{}", msg.as_str()); + self.msg.show_error(msg.as_str())?; + } - self.requires_redraw.set(true); - self.input.set_polling(true); - } - } + self.requires_redraw.set(true); + self.input.set_polling(true); + } + } - Ok(()) - } + Ok(()) + } - //TODO: do we need this? - /// forward ticking to components that require it - pub fn update(&mut self) -> Result<()> { - log::trace!("update"); + //TODO: do we need this? + /// forward ticking to components that require it + pub fn update(&mut self) -> Result<()> { + log::trace!("update"); - self.commit.update(); - self.status_tab.update()?; - self.revlog.update()?; - self.files_tab.update()?; - self.stashing_tab.update()?; - self.stashlist_tab.update()?; + self.commit.update(); + self.status_tab.update()?; + self.revlog.update()?; + self.files_tab.update()?; + self.stashing_tab.update()?; + self.stashlist_tab.update()?; - self.update_commands(); + self.update_commands(); - Ok(()) - } + Ok(()) + } - /// - pub fn update_async( - &mut self, - ev: AsyncNotification, - ) -> Result<()> { - log::trace!("update_async: {:?}", ev); + /// + pub fn update_async( + &mut self, + ev: AsyncNotification, + ) -> Result<()> { + log::trace!("update_async: {:?}", ev); - if let AsyncNotification::Git(ev) = ev { - self.status_tab.update_git(ev)?; - self.stashing_tab.update_git(ev)?; - self.revlog.update_git(ev)?; - self.blame_file_popup.update_git(ev)?; - self.inspect_commit_popup.update_git(ev)?; - self.push_popup.update_git(ev)?; - self.push_tags_popup.update_git(ev)?; - self.pull_popup.update_git(ev)?; - self.select_branch_popup.update_git(ev)?; - } + if let AsyncNotification::Git(ev) = ev { + self.status_tab.update_git(ev)?; + self.stashing_tab.update_git(ev)?; + self.revlog.update_git(ev)?; + self.blame_file_popup.update_git(ev)?; + self.inspect_commit_popup.update_git(ev)?; + self.push_popup.update_git(ev)?; + self.push_tags_popup.update_git(ev)?; + self.pull_popup.update_git(ev)?; + self.select_branch_popup.update_git(ev)?; + } - self.files_tab.update_async(ev); - self.revision_files_popup.update(ev); - self.tags_popup.update(ev); + self.files_tab.update_async(ev); + self.revision_files_popup.update(ev); + self.tags_popup.update(ev); - //TODO: better system for this - // can we simply process the queue here and everyone just uses the queue to schedule a cmd update? - self.process_queue(NeedsUpdate::COMMANDS)?; + //TODO: better system for this + // can we simply process the queue here and everyone just uses the queue to schedule a cmd update? + self.process_queue(NeedsUpdate::COMMANDS)?; - Ok(()) - } + Ok(()) + } - /// - pub const fn is_quit(&self) -> bool { - self.do_quit - } + /// + pub const fn is_quit(&self) -> bool { + self.do_quit + } - /// - pub fn any_work_pending(&self) -> bool { - self.status_tab.anything_pending() - || self.revlog.any_work_pending() - || self.stashing_tab.anything_pending() - || self.files_tab.anything_pending() - || self.blame_file_popup.any_work_pending() - || self.inspect_commit_popup.any_work_pending() - || self.input.is_state_changing() - || self.push_popup.any_work_pending() - || self.push_tags_popup.any_work_pending() - || self.pull_popup.any_work_pending() - || self.revision_files_popup.any_work_pending() - || self.tags_popup.any_work_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.status_tab.anything_pending() + || self.revlog.any_work_pending() + || self.stashing_tab.anything_pending() + || self.files_tab.anything_pending() + || self.blame_file_popup.any_work_pending() + || self.inspect_commit_popup.any_work_pending() + || self.input.is_state_changing() + || self.push_popup.any_work_pending() + || self.push_tags_popup.any_work_pending() + || self.pull_popup.any_work_pending() + || self.revision_files_popup.any_work_pending() + || self.tags_popup.any_work_pending() + } - /// - pub fn requires_redraw(&self) -> bool { - if self.requires_redraw.get() { - self.requires_redraw.set(false); - true - } else { - false - } - } + /// + pub fn requires_redraw(&self) -> bool { + if self.requires_redraw.get() { + self.requires_redraw.set(false); + true + } else { + false + } + } } // private impls impl App { - accessors!( - self, - [ - msg, - reset, - commit, - blame_file_popup, - stashmsg_popup, - inspect_commit_popup, - external_editor_popup, - push_popup, - push_tags_popup, - pull_popup, - tag_commit_popup, - create_branch_popup, - rename_branch_popup, - select_branch_popup, - revision_files_popup, - tags_popup, - help, - revlog, - status_tab, - files_tab, - stashing_tab, - stashlist_tab - ] - ); + accessors!( + self, + [ + msg, + reset, + commit, + blame_file_popup, + stashmsg_popup, + inspect_commit_popup, + external_editor_popup, + push_popup, + push_tags_popup, + pull_popup, + tag_commit_popup, + create_branch_popup, + rename_branch_popup, + select_branch_popup, + revision_files_popup, + tags_popup, + help, + revlog, + status_tab, + files_tab, + stashing_tab, + stashlist_tab + ] + ); - setup_popups!( - self, - [ - commit, - stashmsg_popup, - help, - inspect_commit_popup, - blame_file_popup, - external_editor_popup, - tag_commit_popup, - select_branch_popup, - tags_popup, - create_branch_popup, - rename_branch_popup, - revision_files_popup, - push_popup, - push_tags_popup, - pull_popup, - reset, - msg - ] - ); + setup_popups!( + self, + [ + commit, + stashmsg_popup, + help, + inspect_commit_popup, + blame_file_popup, + external_editor_popup, + tag_commit_popup, + select_branch_popup, + tags_popup, + create_branch_popup, + rename_branch_popup, + revision_files_popup, + push_popup, + push_tags_popup, + pull_popup, + reset, + msg + ] + ); - fn check_quit(&mut self, ev: Event) -> bool { - if self.any_popup_visible() { - return false; - } - if let Event::Key(e) = ev { - if e == self.key_config.quit { - self.do_quit = true; - return true; - } - } - false - } + fn check_quit(&mut self, ev: Event) -> bool { + if self.any_popup_visible() { + return false; + } + if let Event::Key(e) = ev { + if e == self.key_config.quit { + self.do_quit = true; + return true; + } + } + false + } - fn check_hard_exit(&mut self, ev: Event) -> bool { - if let Event::Key(e) = ev { - if e == self.key_config.exit { - self.do_quit = true; - return true; - } - } - false - } + fn check_hard_exit(&mut self, ev: Event) -> bool { + if let Event::Key(e) = ev { + if e == self.key_config.exit { + self.do_quit = true; + return true; + } + } + false + } - fn get_tabs(&mut self) -> Vec<&mut dyn Component> { - vec![ - &mut self.status_tab, - &mut self.revlog, - &mut self.files_tab, - &mut self.stashing_tab, - &mut self.stashlist_tab, - ] - } + fn get_tabs(&mut self) -> Vec<&mut dyn Component> { + vec![ + &mut self.status_tab, + &mut self.revlog, + &mut self.files_tab, + &mut self.stashing_tab, + &mut self.stashlist_tab, + ] + } - fn toggle_tabs(&mut self, reverse: bool) -> Result<()> { - let tabs_len = self.get_tabs().len(); - let new_tab = if reverse { - self.tab.wrapping_sub(1).min(tabs_len.saturating_sub(1)) - } else { - self.tab.saturating_add(1) % tabs_len - }; + fn toggle_tabs(&mut self, reverse: bool) -> Result<()> { + let tabs_len = self.get_tabs().len(); + let new_tab = if reverse { + self.tab.wrapping_sub(1).min(tabs_len.saturating_sub(1)) + } else { + self.tab.saturating_add(1) % tabs_len + }; - self.set_tab(new_tab) - } + self.set_tab(new_tab) + } - fn switch_tab(&mut self, k: KeyEvent) -> Result<()> { - if k == self.key_config.tab_status { - self.set_tab(0)?; - } else if k == self.key_config.tab_log { - self.set_tab(1)?; - } else if k == self.key_config.tab_files { - self.set_tab(2)?; - } else if k == self.key_config.tab_stashing { - self.set_tab(3)?; - } else if k == self.key_config.tab_stashes { - self.set_tab(4)?; - } + fn switch_tab(&mut self, k: KeyEvent) -> Result<()> { + if k == self.key_config.tab_status { + self.set_tab(0)?; + } else if k == self.key_config.tab_log { + self.set_tab(1)?; + } else if k == self.key_config.tab_files { + self.set_tab(2)?; + } else if k == self.key_config.tab_stashing { + self.set_tab(3)?; + } else if k == self.key_config.tab_stashes { + self.set_tab(4)?; + } - Ok(()) - } + Ok(()) + } - fn set_tab(&mut self, tab: usize) -> Result<()> { - let tabs = self.get_tabs(); - for (i, t) in tabs.into_iter().enumerate() { - if tab == i { - t.show()?; - } else { - t.hide(); - } - } + fn set_tab(&mut self, tab: usize) -> Result<()> { + let tabs = self.get_tabs(); + for (i, t) in tabs.into_iter().enumerate() { + if tab == i { + t.show()?; + } else { + t.hide(); + } + } - self.tab = tab; + self.tab = tab; - Ok(()) - } + Ok(()) + } - fn update_commands(&mut self) { - self.help.set_cmds(self.commands(true)); - self.cmdbar.borrow_mut().set_cmds(self.commands(false)); - } + fn update_commands(&mut self) { + self.help.set_cmds(self.commands(true)); + self.cmdbar.borrow_mut().set_cmds(self.commands(false)); + } - fn process_queue(&mut self, flags: NeedsUpdate) -> Result<()> { - let mut flags = flags; - let new_flags = self.process_internal_events()?; - flags.insert(new_flags); + fn process_queue(&mut self, flags: NeedsUpdate) -> Result<()> { + let mut flags = flags; + let new_flags = self.process_internal_events()?; + flags.insert(new_flags); - if flags.contains(NeedsUpdate::ALL) { - self.update()?; - } - //TODO: make this a queue event? - //NOTE: set when any tree component changed selection - if flags.contains(NeedsUpdate::DIFF) { - self.status_tab.update_diff()?; - self.inspect_commit_popup.update_diff()?; - } - if flags.contains(NeedsUpdate::COMMANDS) { - self.update_commands(); - } - if flags.contains(NeedsUpdate::BRANCHES) { - self.select_branch_popup.update_branches()?; - } + if flags.contains(NeedsUpdate::ALL) { + self.update()?; + } + //TODO: make this a queue event? + //NOTE: set when any tree component changed selection + if flags.contains(NeedsUpdate::DIFF) { + self.status_tab.update_diff()?; + self.inspect_commit_popup.update_diff()?; + } + if flags.contains(NeedsUpdate::COMMANDS) { + self.update_commands(); + } + if flags.contains(NeedsUpdate::BRANCHES) { + self.select_branch_popup.update_branches()?; + } - Ok(()) - } + Ok(()) + } - fn process_internal_events(&mut self) -> Result { - let mut flags = NeedsUpdate::empty(); + fn process_internal_events(&mut self) -> Result { + let mut flags = NeedsUpdate::empty(); - loop { - let front = self.queue.pop(); - if let Some(e) = front { - flags.insert(self.process_internal_event(e)?); - } else { - break; - } - } - self.queue.clear(); + loop { + let front = self.queue.pop(); + if let Some(e) = front { + flags.insert(self.process_internal_event(e)?); + } else { + break; + } + } + self.queue.clear(); - Ok(flags) - } + Ok(flags) + } - fn process_internal_event( - &mut self, - ev: InternalEvent, - ) -> Result { - let mut flags = NeedsUpdate::empty(); - match ev { - InternalEvent::ConfirmedAction(action) => { - self.process_confirmed_action(action, &mut flags)?; - } - InternalEvent::ConfirmAction(action) => { - self.reset.open(action)?; - flags.insert(NeedsUpdate::COMMANDS); - } - InternalEvent::ShowErrorMsg(msg) => { - self.msg.show_error(msg.as_str())?; - flags - .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); - } - InternalEvent::Update(u) => flags.insert(u), - InternalEvent::OpenCommit => self.commit.show()?, - InternalEvent::PopupStashing(opts) => { - self.stashmsg_popup.options(opts); - self.stashmsg_popup.show()?; - } - InternalEvent::TagCommit(id) => { - self.tag_commit_popup.open(id)?; - } - InternalEvent::BlameFile(path) => { - self.blame_file_popup.open(&path)?; - flags - .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); - } - InternalEvent::CreateBranch => { - self.create_branch_popup.open()?; - } - InternalEvent::RenameBranch(branch_ref, cur_name) => { - self.rename_branch_popup - .open(branch_ref, cur_name)?; - } - InternalEvent::SelectBranch => { - self.select_branch_popup.open()?; - } - InternalEvent::Tags => { - self.tags_popup.open()?; - } - InternalEvent::TabSwitch => self.set_tab(0)?, - InternalEvent::InspectCommit(id, tags) => { - self.inspect_commit_popup.open(id, tags)?; - flags - .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); - } - InternalEvent::SelectCommitInRevlog(id) => { - if let Err(error) = self.revlog.select_commit(id) { - self.queue.push(InternalEvent::ShowErrorMsg( - error.to_string(), - )); - } else { - self.tags_popup.hide(); - flags.insert(NeedsUpdate::ALL); - } - } - InternalEvent::OpenExternalEditor(path) => { - self.input.set_polling(false); - self.external_editor_popup.show()?; - self.file_to_open = path; - flags.insert(NeedsUpdate::COMMANDS); - } - InternalEvent::Push(branch, force, delete) => { - self.push_popup.push(branch, force, delete)?; - flags.insert(NeedsUpdate::ALL); - } - InternalEvent::Pull(branch) => { - self.pull_popup.fetch(branch)?; - flags.insert(NeedsUpdate::ALL); - } - InternalEvent::PushTags => { - self.push_tags_popup.push_tags()?; - flags.insert(NeedsUpdate::ALL); - } - InternalEvent::StatusLastFileMoved => { - self.status_tab.last_file_moved()?; - } - InternalEvent::OpenFileTree(c) => { - self.revision_files_popup.open(c)?; - flags - .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); - } - }; + fn process_internal_event( + &mut self, + ev: InternalEvent, + ) -> Result { + let mut flags = NeedsUpdate::empty(); + match ev { + InternalEvent::ConfirmedAction(action) => { + self.process_confirmed_action(action, &mut flags)?; + } + InternalEvent::ConfirmAction(action) => { + self.reset.open(action)?; + flags.insert(NeedsUpdate::COMMANDS); + } + InternalEvent::ShowErrorMsg(msg) => { + self.msg.show_error(msg.as_str())?; + flags + .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); + } + InternalEvent::Update(u) => flags.insert(u), + InternalEvent::OpenCommit => self.commit.show()?, + InternalEvent::PopupStashing(opts) => { + self.stashmsg_popup.options(opts); + self.stashmsg_popup.show()?; + } + InternalEvent::TagCommit(id) => { + self.tag_commit_popup.open(id)?; + } + InternalEvent::BlameFile(path) => { + self.blame_file_popup.open(&path)?; + flags + .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); + } + InternalEvent::CreateBranch => { + self.create_branch_popup.open()?; + } + InternalEvent::RenameBranch(branch_ref, cur_name) => { + self.rename_branch_popup + .open(branch_ref, cur_name)?; + } + InternalEvent::SelectBranch => { + self.select_branch_popup.open()?; + } + InternalEvent::Tags => { + self.tags_popup.open()?; + } + InternalEvent::TabSwitch => self.set_tab(0)?, + InternalEvent::InspectCommit(id, tags) => { + self.inspect_commit_popup.open(id, tags)?; + flags + .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); + } + InternalEvent::SelectCommitInRevlog(id) => { + if let Err(error) = self.revlog.select_commit(id) { + self.queue.push(InternalEvent::ShowErrorMsg( + error.to_string(), + )); + } else { + self.tags_popup.hide(); + flags.insert(NeedsUpdate::ALL); + } + } + InternalEvent::OpenExternalEditor(path) => { + self.input.set_polling(false); + self.external_editor_popup.show()?; + self.file_to_open = path; + flags.insert(NeedsUpdate::COMMANDS); + } + InternalEvent::Push(branch, force, delete) => { + self.push_popup.push(branch, force, delete)?; + flags.insert(NeedsUpdate::ALL); + } + InternalEvent::Pull(branch) => { + self.pull_popup.fetch(branch)?; + flags.insert(NeedsUpdate::ALL); + } + InternalEvent::PushTags => { + self.push_tags_popup.push_tags()?; + flags.insert(NeedsUpdate::ALL); + } + InternalEvent::StatusLastFileMoved => { + self.status_tab.last_file_moved()?; + } + InternalEvent::OpenFileTree(c) => { + self.revision_files_popup.open(c)?; + flags + .insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS); + } + }; - Ok(flags) - } + Ok(flags) + } - fn process_confirmed_action( - &mut self, - action: Action, - flags: &mut NeedsUpdate, - ) -> Result<()> { - match action { - Action::Reset(r) => { - if self.status_tab.reset(&r) { - flags.insert(NeedsUpdate::ALL); - } - } - Action::StashDrop(_) | Action::StashPop(_) => { - if self.stashlist_tab.action_confirmed(&action) { - flags.insert(NeedsUpdate::ALL); - } - } - Action::ResetHunk(path, hash) => { - sync::reset_hunk(CWD, &path, hash)?; - flags.insert(NeedsUpdate::ALL); - } - Action::ResetLines(path, lines) => { - sync::discard_lines(CWD, &path, &lines)?; - flags.insert(NeedsUpdate::ALL); - } - Action::DeleteBranch(branch_ref, true) => { - if let Err(e) = sync::delete_branch(CWD, &branch_ref) - { - self.queue.push(InternalEvent::ShowErrorMsg( - e.to_string(), - )); - } - flags.insert(NeedsUpdate::ALL); - self.select_branch_popup.update_branches()?; - } - Action::DeleteBranch(branch_ref, false) => { - self.queue.push( - if let Some(name) = branch_ref.rsplit('/').next() - { - InternalEvent::Push( - name.to_string(), - false, - true, - ) - } else { - InternalEvent::ShowErrorMsg(format!( - "Failed to find the branch name in {}", - branch_ref - )) - }, - ); - flags.insert(NeedsUpdate::ALL); - self.select_branch_popup.update_branches()?; - } - Action::DeleteTag(tag_name) => { - if let Err(error) = sync::delete_tag(CWD, &tag_name) { - self.queue.push(InternalEvent::ShowErrorMsg( - error.to_string(), - )); - } else { - flags.insert(NeedsUpdate::ALL); - self.tags_popup.update_tags()?; - } - } - Action::ForcePush(branch, force) => { - self.queue - .push(InternalEvent::Push(branch, force, false)); - } - Action::PullMerge { rebase, .. } => { - self.pull_popup.try_conflict_free_merge(rebase); - flags.insert(NeedsUpdate::ALL); - } - Action::AbortMerge => { - self.status_tab.abort_merge(); - flags.insert(NeedsUpdate::ALL); - } - }; + fn process_confirmed_action( + &mut self, + action: Action, + flags: &mut NeedsUpdate, + ) -> Result<()> { + match action { + Action::Reset(r) => { + if self.status_tab.reset(&r) { + flags.insert(NeedsUpdate::ALL); + } + } + Action::StashDrop(_) | Action::StashPop(_) => { + if self.stashlist_tab.action_confirmed(&action) { + flags.insert(NeedsUpdate::ALL); + } + } + Action::ResetHunk(path, hash) => { + sync::reset_hunk(CWD, &path, hash)?; + flags.insert(NeedsUpdate::ALL); + } + Action::ResetLines(path, lines) => { + sync::discard_lines(CWD, &path, &lines)?; + flags.insert(NeedsUpdate::ALL); + } + Action::DeleteBranch(branch_ref, true) => { + if let Err(e) = sync::delete_branch(CWD, &branch_ref) + { + self.queue.push(InternalEvent::ShowErrorMsg( + e.to_string(), + )); + } + flags.insert(NeedsUpdate::ALL); + self.select_branch_popup.update_branches()?; + } + Action::DeleteBranch(branch_ref, false) => { + self.queue.push( + if let Some(name) = branch_ref.rsplit('/').next() + { + InternalEvent::Push( + name.to_string(), + false, + true, + ) + } else { + InternalEvent::ShowErrorMsg(format!( + "Failed to find the branch name in {}", + branch_ref + )) + }, + ); + flags.insert(NeedsUpdate::ALL); + self.select_branch_popup.update_branches()?; + } + Action::DeleteTag(tag_name) => { + if let Err(error) = sync::delete_tag(CWD, &tag_name) { + self.queue.push(InternalEvent::ShowErrorMsg( + error.to_string(), + )); + } else { + flags.insert(NeedsUpdate::ALL); + self.tags_popup.update_tags()?; + } + } + Action::ForcePush(branch, force) => { + self.queue + .push(InternalEvent::Push(branch, force, false)); + } + Action::PullMerge { rebase, .. } => { + self.pull_popup.try_conflict_free_merge(rebase); + flags.insert(NeedsUpdate::ALL); + } + Action::AbortMerge => { + self.status_tab.abort_merge(); + flags.insert(NeedsUpdate::ALL); + } + }; - Ok(()) - } + Ok(()) + } - fn commands(&self, force_all: bool) -> Vec { - let mut res = Vec::new(); + fn commands(&self, force_all: bool) -> Vec { + let mut res = Vec::new(); - for c in self.components() { - if c.commands(&mut res, force_all) - != CommandBlocking::PassingOn - && !force_all - { - break; - } - } + for c in self.components() { + if c.commands(&mut res, force_all) + != CommandBlocking::PassingOn + && !force_all + { + break; + } + } - res.push( - CommandInfo::new( - strings::commands::toggle_tabs(&self.key_config), - true, - !self.any_popup_visible(), - ) - .order(order::NAV), - ); - res.push( - CommandInfo::new( - strings::commands::toggle_tabs_direct( - &self.key_config, - ), - true, - !self.any_popup_visible(), - ) - .order(order::NAV), - ); + res.push( + CommandInfo::new( + strings::commands::toggle_tabs(&self.key_config), + true, + !self.any_popup_visible(), + ) + .order(order::NAV), + ); + res.push( + CommandInfo::new( + strings::commands::toggle_tabs_direct( + &self.key_config, + ), + true, + !self.any_popup_visible(), + ) + .order(order::NAV), + ); - res.push( - CommandInfo::new( - strings::commands::quit(&self.key_config), - true, - !self.any_popup_visible(), - ) - .order(100), - ); + res.push( + CommandInfo::new( + strings::commands::quit(&self.key_config), + true, + !self.any_popup_visible(), + ) + .order(100), + ); - res - } + res + } - //TODO: make this dynamic - fn draw_tabs(&self, f: &mut Frame, r: Rect) { - let r = r.inner(&Margin { - vertical: 0, - horizontal: 1, - }); + //TODO: make this dynamic + fn draw_tabs(&self, f: &mut Frame, r: Rect) { + let r = r.inner(&Margin { + vertical: 0, + horizontal: 1, + }); - let tabs = [ - Span::raw(strings::tab_status(&self.key_config)), - Span::raw(strings::tab_log(&self.key_config)), - Span::raw(strings::tab_files(&self.key_config)), - Span::raw(strings::tab_stashing(&self.key_config)), - Span::raw(strings::tab_stashes(&self.key_config)), - ] - .iter() - .cloned() - .map(Spans::from) - .collect(); + let tabs = [ + Span::raw(strings::tab_status(&self.key_config)), + Span::raw(strings::tab_log(&self.key_config)), + Span::raw(strings::tab_files(&self.key_config)), + Span::raw(strings::tab_stashing(&self.key_config)), + Span::raw(strings::tab_stashes(&self.key_config)), + ] + .iter() + .cloned() + .map(Spans::from) + .collect(); - f.render_widget( - Tabs::new(tabs) - .block( - Block::default() - .borders(Borders::BOTTOM) - .border_style(self.theme.block(false)), - ) - .style(self.theme.tab(false)) - .highlight_style(self.theme.tab(true)) - .divider(strings::tab_divider(&self.key_config)) - .select(self.tab), - r, - ); - } + f.render_widget( + Tabs::new(tabs) + .block( + Block::default() + .borders(Borders::BOTTOM) + .border_style(self.theme.block(false)), + ) + .style(self.theme.tab(false)) + .highlight_style(self.theme.tab(true)) + .divider(strings::tab_divider(&self.key_config)) + .select(self.tab), + r, + ); + } } diff --git a/src/args.rs b/src/args.rs index 57e5be83..67b2e294 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,109 +1,109 @@ use crate::bug_report; use anyhow::{anyhow, Result}; use clap::{ - crate_authors, crate_description, crate_name, crate_version, - App as ClapApp, Arg, + crate_authors, crate_description, crate_name, crate_version, + App as ClapApp, Arg, }; use simplelog::{Config, LevelFilter, WriteLogger}; use std::{ - env, - fs::{self, File}, - path::PathBuf, + env, + fs::{self, File}, + path::PathBuf, }; pub struct CliArgs { - pub theme: PathBuf, + pub theme: PathBuf, } pub fn process_cmdline() -> Result { - let app = ClapApp::new(crate_name!()) - .author(crate_authors!()) - .version(crate_version!()) - .about(crate_description!()) - .arg( - Arg::with_name("theme") - .help("Set the color theme (defaults to theme.ron)") - .short("t") - .long("theme") - .value_name("THEME") - .takes_value(true), - ) - .arg( - Arg::with_name("logging") - .help("Stores logging output into a cache directory") - .short("l") - .long("logging"), - ) - .arg( - Arg::with_name("bugreport") - .help("Generate a bug report") - .long("bugreport"), - ) - .arg( - Arg::with_name("directory") - .help("Set the working directory") - .short("d") - .long("directory") - .takes_value(true), - ); + let app = ClapApp::new(crate_name!()) + .author(crate_authors!()) + .version(crate_version!()) + .about(crate_description!()) + .arg( + Arg::with_name("theme") + .help("Set the color theme (defaults to theme.ron)") + .short("t") + .long("theme") + .value_name("THEME") + .takes_value(true), + ) + .arg( + Arg::with_name("logging") + .help("Stores logging output into a cache directory") + .short("l") + .long("logging"), + ) + .arg( + Arg::with_name("bugreport") + .help("Generate a bug report") + .long("bugreport"), + ) + .arg( + Arg::with_name("directory") + .help("Set the working directory") + .short("d") + .long("directory") + .takes_value(true), + ); - let arg_matches = app.get_matches(); - if arg_matches.is_present("bugreport") { - bug_report::generate_bugreport(); - std::process::exit(0); - } - if arg_matches.is_present("logging") { - setup_logging()?; - } - if arg_matches.is_present("directory") { - let directory = - arg_matches.value_of("directory").unwrap_or("."); - env::set_current_dir(directory)?; - } - let arg_theme = - arg_matches.value_of("theme").unwrap_or("theme.ron"); - if get_app_config_path()?.join(arg_theme).is_file() { - Ok(CliArgs { - theme: get_app_config_path()?.join(arg_theme), - }) - } else { - Ok(CliArgs { - theme: get_app_config_path()?.join("theme.ron"), - }) - } + let arg_matches = app.get_matches(); + if arg_matches.is_present("bugreport") { + bug_report::generate_bugreport(); + std::process::exit(0); + } + if arg_matches.is_present("logging") { + setup_logging()?; + } + if arg_matches.is_present("directory") { + let directory = + arg_matches.value_of("directory").unwrap_or("."); + env::set_current_dir(directory)?; + } + let arg_theme = + arg_matches.value_of("theme").unwrap_or("theme.ron"); + if get_app_config_path()?.join(arg_theme).is_file() { + Ok(CliArgs { + theme: get_app_config_path()?.join(arg_theme), + }) + } else { + Ok(CliArgs { + theme: get_app_config_path()?.join("theme.ron"), + }) + } } fn setup_logging() -> Result<()> { - let mut path = get_app_cache_path()?; - path.push("gitui.log"); + let mut path = get_app_cache_path()?; + path.push("gitui.log"); - let _ = WriteLogger::init( - LevelFilter::Trace, - Config::default(), - File::create(path)?, - ); + let _ = WriteLogger::init( + LevelFilter::Trace, + Config::default(), + File::create(path)?, + ); - Ok(()) + Ok(()) } fn get_app_cache_path() -> Result { - let mut path = dirs_next::cache_dir() - .ok_or_else(|| anyhow!("failed to find os cache dir."))?; + let mut path = dirs_next::cache_dir() + .ok_or_else(|| anyhow!("failed to find os cache dir."))?; - path.push("gitui"); - fs::create_dir_all(&path)?; - Ok(path) + path.push("gitui"); + fs::create_dir_all(&path)?; + Ok(path) } pub fn get_app_config_path() -> Result { - let mut path = if cfg!(target_os = "macos") { - dirs_next::home_dir().map(|h| h.join(".config")) - } else { - dirs_next::config_dir() - } - .ok_or_else(|| anyhow!("failed to find os config dir."))?; + let mut path = if cfg!(target_os = "macos") { + dirs_next::home_dir().map(|h| h.join(".config")) + } else { + dirs_next::config_dir() + } + .ok_or_else(|| anyhow!("failed to find os config dir."))?; - path.push("gitui"); - fs::create_dir_all(&path)?; - Ok(path) + path.push("gitui"); + fs::create_dir_all(&path)?; + Ok(path) } diff --git a/src/bug_report.rs b/src/bug_report.rs index 19da075e..f0607d75 100644 --- a/src/bug_report.rs +++ b/src/bug_report.rs @@ -1,23 +1,23 @@ use bugreport::{ - bugreport, - collector::{ - CommandLine, CompileTimeInformation, EnvironmentVariables, - OperatingSystem, SoftwareVersion, - }, - format::Markdown, + bugreport, + collector::{ + CommandLine, CompileTimeInformation, EnvironmentVariables, + OperatingSystem, SoftwareVersion, + }, + format::Markdown, }; pub fn generate_bugreport() { - bugreport!() - .info(SoftwareVersion::default()) - .info(OperatingSystem::default()) - .info(CompileTimeInformation::default()) - .info(EnvironmentVariables::list(&[ - "SHELL", - "EDITOR", - "GIT_EDITOR", - "VISUAL", - ])) - .info(CommandLine::default()) - .print::(); + bugreport!() + .info(SoftwareVersion::default()) + .info(OperatingSystem::default()) + .info(CompileTimeInformation::default()) + .info(EnvironmentVariables::list(&[ + "SHELL", + "EDITOR", + "GIT_EDITOR", + "VISUAL", + ])) + .info(CommandLine::default()) + .print::(); } diff --git a/src/clipboard.rs b/src/clipboard.rs index a0492e61..a7ab23fa 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -6,69 +6,69 @@ use std::io::Write; use std::process::{Command, Stdio}; fn execute_copy_command(command: Command, text: &str) -> Result<()> { - let mut command = command; + let mut command = command; - let mut process = command - .stdin(Stdio::piped()) - .stdout(Stdio::null()) - .spawn() - .map_err(|e| anyhow!("`{:?}`: {}", command, e))?; + let mut process = command + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .spawn() + .map_err(|e| anyhow!("`{:?}`: {}", command, e))?; - process - .stdin - .as_mut() - .ok_or_else(|| anyhow!("`{:?}`", command))? - .write_all(text.as_bytes()) - .map_err(|e| anyhow!("`{:?}`: {}", command, e))?; + process + .stdin + .as_mut() + .ok_or_else(|| anyhow!("`{:?}`", command))? + .write_all(text.as_bytes()) + .map_err(|e| anyhow!("`{:?}`: {}", command, e))?; - process - .wait() - .map_err(|e| anyhow!("`{:?}`: {}", command, e))?; + process + .wait() + .map_err(|e| anyhow!("`{:?}`: {}", command, e))?; - Ok(()) + Ok(()) } #[cfg(all(target_family = "unix", not(target_os = "macos")))] fn gen_command( - path: impl AsRef, - xclip_syntax: bool, + path: impl AsRef, + xclip_syntax: bool, ) -> Command { - let mut c = Command::new(path); - if xclip_syntax { - c.arg("-selection"); - c.arg("clipboard"); - } else { - c.arg("--clipboard"); - } - c + let mut c = Command::new(path); + if xclip_syntax { + c.arg("-selection"); + c.arg("clipboard"); + } else { + c.arg("--clipboard"); + } + c } #[cfg(all(target_family = "unix", not(target_os = "macos")))] pub fn copy_string(string: &str) -> Result<()> { - use std::path::PathBuf; - use which::which; - let (path, xclip_syntax) = which("xclip").ok().map_or_else( - || { - ( - which("xsel") - .ok() - .unwrap_or_else(|| PathBuf::from("xsel")), - false, - ) - }, - |path| (path, true), - ); + use std::path::PathBuf; + use which::which; + let (path, xclip_syntax) = which("xclip").ok().map_or_else( + || { + ( + which("xsel") + .ok() + .unwrap_or_else(|| PathBuf::from("xsel")), + false, + ) + }, + |path| (path, true), + ); - let cmd = gen_command(path, xclip_syntax); - execute_copy_command(cmd, string) + let cmd = gen_command(path, xclip_syntax); + execute_copy_command(cmd, string) } #[cfg(target_os = "macos")] pub fn copy_string(string: &str) -> Result<()> { - execute_copy_command(Command::new("pbcopy"), string) + execute_copy_command(Command::new("pbcopy"), string) } #[cfg(windows)] pub fn copy_string(string: &str) -> Result<()> { - execute_copy_command(Command::new("clip"), string) + execute_copy_command(Command::new("clip"), string) } diff --git a/src/cmdbar.rs b/src/cmdbar.rs index dc227723..fa8b2842 100644 --- a/src/cmdbar.rs +++ b/src/cmdbar.rs @@ -1,205 +1,205 @@ use crate::{ - components::CommandInfo, keys::SharedKeyConfig, strings, - ui::style::SharedTheme, + components::CommandInfo, keys::SharedKeyConfig, strings, + ui::style::SharedTheme, }; use std::borrow::Cow; use tui::{ - backend::Backend, - layout::{Alignment, Rect}, - text::{Span, Spans}, - widgets::Paragraph, - Frame, + backend::Backend, + layout::{Alignment, Rect}, + text::{Span, Spans}, + widgets::Paragraph, + Frame, }; use unicode_width::UnicodeWidthStr; enum DrawListEntry { - LineBreak, - Splitter, - Command(Command), + LineBreak, + Splitter, + Command(Command), } struct Command { - txt: String, - enabled: bool, - line: usize, + txt: String, + enabled: bool, + line: usize, } /// helper to be used while drawing pub struct CommandBar { - draw_list: Vec, - cmd_infos: Vec, - theme: SharedTheme, - key_config: SharedKeyConfig, - lines: u16, - width: u16, - expandable: bool, - expanded: bool, + draw_list: Vec, + cmd_infos: Vec, + theme: SharedTheme, + key_config: SharedKeyConfig, + lines: u16, + width: u16, + expandable: bool, + expanded: bool, } const MORE_WIDTH: u16 = 9; impl CommandBar { - pub const fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - draw_list: Vec::new(), - cmd_infos: Vec::new(), - theme, - key_config, - lines: 0, - width: 0, - expandable: false, - expanded: false, - } - } + pub const fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + draw_list: Vec::new(), + cmd_infos: Vec::new(), + theme, + key_config, + lines: 0, + width: 0, + expandable: false, + expanded: false, + } + } - pub fn refresh_width(&mut self, width: u16) { - if width != self.width { - self.refresh_list(width); - self.width = width; - } - } + pub fn refresh_width(&mut self, width: u16) { + if width != self.width { + self.refresh_list(width); + self.width = width; + } + } - fn is_multiline(&self, width: u16) -> bool { - let mut line_width = 0_usize; - for c in &self.cmd_infos { - let entry_w = - UnicodeWidthStr::width(c.text.name.as_str()); + fn is_multiline(&self, width: u16) -> bool { + let mut line_width = 0_usize; + for c in &self.cmd_infos { + let entry_w = + UnicodeWidthStr::width(c.text.name.as_str()); - if line_width + entry_w > width as usize { - return true; - } + if line_width + entry_w > width as usize { + return true; + } - line_width += entry_w + 1; - } + line_width += entry_w + 1; + } - false - } + false + } - fn refresh_list(&mut self, width: u16) { - self.draw_list.clear(); + fn refresh_list(&mut self, width: u16) { + self.draw_list.clear(); - let width = if self.is_multiline(width) { - width.saturating_sub(MORE_WIDTH) - } else { - width - }; + let width = if self.is_multiline(width) { + width.saturating_sub(MORE_WIDTH) + } else { + width + }; - let mut line_width = 0_usize; - let mut lines = 1_u16; + let mut line_width = 0_usize; + let mut lines = 1_u16; - for c in &self.cmd_infos { - let entry_w = - UnicodeWidthStr::width(c.text.name.as_str()); + for c in &self.cmd_infos { + let entry_w = + UnicodeWidthStr::width(c.text.name.as_str()); - if line_width + entry_w > width as usize { - self.draw_list.push(DrawListEntry::LineBreak); - line_width = 0; - lines += 1; - } else if line_width > 0 { - self.draw_list.push(DrawListEntry::Splitter); - } + if line_width + entry_w > width as usize { + self.draw_list.push(DrawListEntry::LineBreak); + line_width = 0; + lines += 1; + } else if line_width > 0 { + self.draw_list.push(DrawListEntry::Splitter); + } - line_width += entry_w + 1; + line_width += entry_w + 1; - self.draw_list.push(DrawListEntry::Command(Command { - txt: c.text.name.to_string(), - enabled: c.enabled, - line: lines.saturating_sub(1) as usize, - })); - } + self.draw_list.push(DrawListEntry::Command(Command { + txt: c.text.name.to_string(), + enabled: c.enabled, + line: lines.saturating_sub(1) as usize, + })); + } - self.expandable = lines > 1; + self.expandable = lines > 1; - self.lines = lines; - } + self.lines = lines; + } - pub fn set_cmds(&mut self, cmds: Vec) { - self.cmd_infos = cmds - .into_iter() - .filter(CommandInfo::show_in_quickbar) - .collect::>(); - self.cmd_infos.sort_by_key(|e| e.order); - self.refresh_list(self.width); - } + pub fn set_cmds(&mut self, cmds: Vec) { + self.cmd_infos = cmds + .into_iter() + .filter(CommandInfo::show_in_quickbar) + .collect::>(); + self.cmd_infos.sort_by_key(|e| e.order); + self.refresh_list(self.width); + } - pub const fn height(&self) -> u16 { - if self.expandable && self.expanded { - self.lines - } else { - 1_u16 - } - } + pub const fn height(&self) -> u16 { + if self.expandable && self.expanded { + self.lines + } else { + 1_u16 + } + } - pub fn toggle_more(&mut self) { - if self.expandable { - self.expanded = !self.expanded; - } - } + pub fn toggle_more(&mut self) { + if self.expandable { + self.expanded = !self.expanded; + } + } - pub fn draw(&self, f: &mut Frame, r: Rect) { - if r.width < MORE_WIDTH { - return; - } - let splitter = Span::raw(Cow::from(strings::cmd_splitter( - &self.key_config, - ))); + pub fn draw(&self, f: &mut Frame, r: Rect) { + if r.width < MORE_WIDTH { + return; + } + let splitter = Span::raw(Cow::from(strings::cmd_splitter( + &self.key_config, + ))); - let texts = self - .draw_list - .split(|c| matches!(c, DrawListEntry::LineBreak)) - .map(|c_arr| { - Spans::from( - c_arr - .iter() - .map(|c| match c { - DrawListEntry::Command(c) => { - Span::styled( - Cow::from(c.txt.as_str()), - self.theme.commandbar( - c.enabled, c.line, - ), - ) - } - DrawListEntry::LineBreak => { - // Doesn't exist in split array - Span::raw("") - } - DrawListEntry::Splitter => { - splitter.clone() - } - }) - .collect::>(), - ) - }) - .collect::>(); + let texts = self + .draw_list + .split(|c| matches!(c, DrawListEntry::LineBreak)) + .map(|c_arr| { + Spans::from( + c_arr + .iter() + .map(|c| match c { + DrawListEntry::Command(c) => { + Span::styled( + Cow::from(c.txt.as_str()), + self.theme.commandbar( + c.enabled, c.line, + ), + ) + } + DrawListEntry::LineBreak => { + // Doesn't exist in split array + Span::raw("") + } + DrawListEntry::Splitter => { + splitter.clone() + } + }) + .collect::>(), + ) + }) + .collect::>(); - f.render_widget( - Paragraph::new(texts).alignment(Alignment::Left), - r, - ); + f.render_widget( + Paragraph::new(texts).alignment(Alignment::Left), + r, + ); - if self.expandable { - let r = Rect::new( - r.width.saturating_sub(MORE_WIDTH), - r.y + r.height.saturating_sub(1), - MORE_WIDTH.min(r.width), - 1.min(r.height), - ); + if self.expandable { + let r = Rect::new( + r.width.saturating_sub(MORE_WIDTH), + r.y + r.height.saturating_sub(1), + MORE_WIDTH.min(r.width), + 1.min(r.height), + ); - f.render_widget( - Paragraph::new(Spans::from(vec![Span::raw( - Cow::from(if self.expanded { - "less [.]" - } else { - "more [.]" - }), - )])) - .alignment(Alignment::Right), - r, - ); - } - } + f.render_widget( + Paragraph::new(Spans::from(vec![Span::raw( + Cow::from(if self.expanded { + "less [.]" + } else { + "more [.]" + }), + )])) + .alignment(Alignment::Right), + r, + ); + } + } } diff --git a/src/components/blame_file.rs b/src/components/blame_file.rs index df69f4e9..be1a0151 100644 --- a/src/components/blame_file.rs +++ b/src/components/blame_file.rs @@ -1,42 +1,42 @@ use super::{ - utils, visibility_blocking, CommandBlocking, CommandInfo, - Component, DrawableComponent, EventState, + utils, visibility_blocking, CommandBlocking, CommandInfo, + Component, DrawableComponent, EventState, }; use crate::{ - components::{utils::string_width_align, ScrollType}, - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings, - ui::{self, style::SharedTheme}, + components::{utils::string_width_align, ScrollType}, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::{self, style::SharedTheme}, }; use anyhow::Result; use asyncgit::{ - sync::{BlameHunk, CommitId, FileBlame}, - AsyncBlame, AsyncGitNotification, BlameParams, + sync::{BlameHunk, CommitId, FileBlame}, + AsyncBlame, AsyncGitNotification, BlameParams, }; use crossbeam_channel::Sender; use crossterm::event::Event; use std::convert::TryInto; use tui::{ - backend::Backend, - layout::{Constraint, Rect}, - symbols::line::VERTICAL, - text::Span, - widgets::{Block, Borders, Cell, Clear, Row, Table, TableState}, - Frame, + backend::Backend, + layout::{Constraint, Rect}, + symbols::line::VERTICAL, + text::Span, + widgets::{Block, Borders, Cell, Clear, Row, Table, TableState}, + Frame, }; pub struct BlameFileComponent { - title: String, - theme: SharedTheme, - queue: Queue, - async_blame: AsyncBlame, - visible: bool, - file_path: Option, - file_blame: Option, - table_state: std::cell::Cell, - key_config: SharedKeyConfig, - current_height: std::cell::Cell, + title: String, + theme: SharedTheme, + queue: Queue, + async_blame: AsyncBlame, + visible: bool, + file_path: Option, + file_blame: Option, + table_state: std::cell::Cell, + key_config: SharedKeyConfig, + current_height: std::cell::Cell, } static NO_COMMIT_ID: &str = "0000000"; @@ -45,482 +45,482 @@ static MIN_AUTHOR_WIDTH: usize = 3; static MAX_AUTHOR_WIDTH: usize = 20; fn get_author_width(width: usize) -> usize { - (width.saturating_sub(19) / 3) - .clamp(MIN_AUTHOR_WIDTH, MAX_AUTHOR_WIDTH) + (width.saturating_sub(19) / 3) + .clamp(MIN_AUTHOR_WIDTH, MAX_AUTHOR_WIDTH) } const fn number_of_digits(number: usize) -> usize { - let mut rest = number; - let mut result = 0; + let mut rest = number; + let mut result = 0; - while rest > 0 { - rest /= 10; - result += 1; - } + while rest > 0 { + rest /= 10; + result += 1; + } - result + result } impl DrawableComponent for BlameFileComponent { - fn draw( - &self, - f: &mut Frame, - area: Rect, - ) -> Result<()> { - if self.is_visible() { - let title = self.get_title(); + fn draw( + &self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { + if self.is_visible() { + let title = self.get_title(); - let rows = self.get_rows(area.width.into()); - let author_width = get_author_width(area.width.into()); - let constraints = [ - // commit id - Constraint::Length(7), - // commit date - Constraint::Length(10), - // commit author - Constraint::Length(author_width.try_into()?), - // line number and vertical bar - Constraint::Length( - (self.get_line_number_width().saturating_add(1)) - .try_into()?, - ), - // the source code line - Constraint::Percentage(100), - ]; + let rows = self.get_rows(area.width.into()); + let author_width = get_author_width(area.width.into()); + let constraints = [ + // commit id + Constraint::Length(7), + // commit date + Constraint::Length(10), + // commit author + Constraint::Length(author_width.try_into()?), + // line number and vertical bar + Constraint::Length( + (self.get_line_number_width().saturating_add(1)) + .try_into()?, + ), + // the source code line + Constraint::Percentage(100), + ]; - let number_of_rows = rows.len(); + let number_of_rows = rows.len(); - let table = Table::new(rows) - .widths(&constraints) - .column_spacing(1) - .highlight_style(self.theme.text(true, true)) - .block( - Block::default() - .borders(Borders::ALL) - .title(Span::styled( - title, - self.theme.title(true), - )) - .border_style(self.theme.block(true)), - ); + let table = Table::new(rows) + .widths(&constraints) + .column_spacing(1) + .highlight_style(self.theme.text(true, true)) + .block( + Block::default() + .borders(Borders::ALL) + .title(Span::styled( + title, + self.theme.title(true), + )) + .border_style(self.theme.block(true)), + ); - let mut table_state = self.table_state.take(); + let mut table_state = self.table_state.take(); - f.render_widget(Clear, area); - f.render_stateful_widget(table, area, &mut table_state); + f.render_widget(Clear, area); + f.render_stateful_widget(table, area, &mut table_state); - ui::draw_scrollbar( - f, - area, - &self.theme, - // April 2021: `draw_scrollbar` assumes that the last parameter - // is `scroll_top`. Therefore, it subtracts the area’s height - // before calculating the position of the scrollbar. To account - // for that, we add the current height. - number_of_rows + (area.height as usize), - // April 2021: we don’t have access to `table_state.offset` - // (it’s private), so we use `table_state.selected()` as a - // replacement. - // - // Other widgets, for example `BranchListComponent`, manage - // scroll state themselves and use `self.scroll_top` in this - // situation. - // - // There are plans to change `render_stateful_widgets`, so this - // might be acceptable as an interim solution. - // - // https://github.com/fdehau/tui-rs/issues/448 - table_state.selected().unwrap_or(0), - ); + ui::draw_scrollbar( + f, + area, + &self.theme, + // April 2021: `draw_scrollbar` assumes that the last parameter + // is `scroll_top`. Therefore, it subtracts the area’s height + // before calculating the position of the scrollbar. To account + // for that, we add the current height. + number_of_rows + (area.height as usize), + // April 2021: we don’t have access to `table_state.offset` + // (it’s private), so we use `table_state.selected()` as a + // replacement. + // + // Other widgets, for example `BranchListComponent`, manage + // scroll state themselves and use `self.scroll_top` in this + // situation. + // + // There are plans to change `render_stateful_widgets`, so this + // might be acceptable as an interim solution. + // + // https://github.com/fdehau/tui-rs/issues/448 + table_state.selected().unwrap_or(0), + ); - self.table_state.set(table_state); - self.current_height.set(area.height.into()); - } + self.table_state.set(table_state); + self.current_height.set(area.height.into()); + } - Ok(()) - } + Ok(()) + } } impl Component for BlameFileComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - out.push( - CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - ) - .order(1), - ); - out.push( - CommandInfo::new( - strings::commands::scroll(&self.key_config), - true, - self.file_blame.is_some(), - ) - .order(1), - ); - out.push( - CommandInfo::new( - strings::commands::log_details_open( - &self.key_config, - ), - true, - self.file_blame.is_some(), - ) - .order(1), - ); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + out.push( + CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + ) + .order(1), + ); + out.push( + CommandInfo::new( + strings::commands::scroll(&self.key_config), + true, + self.file_blame.is_some(), + ) + .order(1), + ); + out.push( + CommandInfo::new( + strings::commands::log_details_open( + &self.key_config, + ), + true, + self.file_blame.is_some(), + ) + .order(1), + ); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event( - &mut self, - event: crossterm::event::Event, - ) -> Result { - if self.is_visible() { - if let Event::Key(key) = event { - if key == self.key_config.exit_popup { - self.hide(); - } else if key == self.key_config.move_up { - self.move_selection(ScrollType::Up); - } else if key == self.key_config.move_down { - self.move_selection(ScrollType::Down); - } else if key == self.key_config.shift_up - || key == self.key_config.home - { - self.move_selection(ScrollType::Home); - } else if key == self.key_config.shift_down - || key == self.key_config.end - { - self.move_selection(ScrollType::End); - } else if key == self.key_config.page_down { - self.move_selection(ScrollType::PageDown); - } else if key == self.key_config.page_up { - self.move_selection(ScrollType::PageUp); - } else if key == self.key_config.focus_right { - self.hide(); + fn event( + &mut self, + event: crossterm::event::Event, + ) -> Result { + if self.is_visible() { + if let Event::Key(key) = event { + if key == self.key_config.exit_popup { + self.hide(); + } else if key == self.key_config.move_up { + self.move_selection(ScrollType::Up); + } else if key == self.key_config.move_down { + self.move_selection(ScrollType::Down); + } else if key == self.key_config.shift_up + || key == self.key_config.home + { + self.move_selection(ScrollType::Home); + } else if key == self.key_config.shift_down + || key == self.key_config.end + { + self.move_selection(ScrollType::End); + } else if key == self.key_config.page_down { + self.move_selection(ScrollType::PageDown); + } else if key == self.key_config.page_up { + self.move_selection(ScrollType::PageUp); + } else if key == self.key_config.focus_right { + self.hide(); - return self.selected_commit().map_or( - Ok(EventState::NotConsumed), - |id| { - self.queue.push( - InternalEvent::InspectCommit( - id, None, - ), - ); - Ok(EventState::Consumed) - }, - ); - } + return self.selected_commit().map_or( + Ok(EventState::NotConsumed), + |id| { + self.queue.push( + InternalEvent::InspectCommit( + id, None, + ), + ); + Ok(EventState::Consumed) + }, + ); + } - return Ok(EventState::Consumed); - } - } + return Ok(EventState::Consumed); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } impl BlameFileComponent { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - title: &str, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - title: String::from(title), - theme, - async_blame: AsyncBlame::new(sender), - queue: queue.clone(), - visible: false, - file_path: None, - file_blame: None, - table_state: std::cell::Cell::new(TableState::default()), - key_config, - current_height: std::cell::Cell::new(0), - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + title: &str, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + title: String::from(title), + theme, + async_blame: AsyncBlame::new(sender), + queue: queue.clone(), + visible: false, + file_path: None, + file_blame: None, + table_state: std::cell::Cell::new(TableState::default()), + key_config, + current_height: std::cell::Cell::new(0), + } + } - /// - pub fn open(&mut self, file_path: &str) -> Result<()> { - self.file_path = Some(file_path.into()); - self.file_blame = None; - self.table_state.get_mut().select(Some(0)); - self.show()?; + /// + pub fn open(&mut self, file_path: &str) -> Result<()> { + self.file_path = Some(file_path.into()); + self.file_blame = None; + self.table_state.get_mut().select(Some(0)); + self.show()?; - self.update()?; + self.update()?; - Ok(()) - } + Ok(()) + } - /// - pub fn any_work_pending(&self) -> bool { - self.async_blame.is_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.async_blame.is_pending() + } - /// - pub fn update_git( - &mut self, - event: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::Blame = event { - self.update()?; - } - } + /// + pub fn update_git( + &mut self, + event: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::Blame = event { + self.update()?; + } + } - Ok(()) - } + Ok(()) + } - fn update(&mut self) -> Result<()> { - if self.is_visible() { - if let Some(file_path) = &self.file_path { - let blame_params = BlameParams { - file_path: file_path.into(), - }; + fn update(&mut self) -> Result<()> { + if self.is_visible() { + if let Some(file_path) = &self.file_path { + let blame_params = BlameParams { + file_path: file_path.into(), + }; - if let Some(( - previous_blame_params, - last_file_blame, - )) = self.async_blame.last()? - { - if previous_blame_params == blame_params { - self.file_blame = Some(last_file_blame); + if let Some(( + previous_blame_params, + last_file_blame, + )) = self.async_blame.last()? + { + if previous_blame_params == blame_params { + self.file_blame = Some(last_file_blame); - return Ok(()); - } - } + return Ok(()); + } + } - self.async_blame.request(blame_params)?; - } - } + self.async_blame.request(blame_params)?; + } + } - Ok(()) - } + Ok(()) + } - /// - fn get_title(&self) -> String { - match ( - self.any_work_pending(), - self.file_path.as_ref(), - self.file_blame.as_ref(), - ) { - (true, Some(file_path), _) => { - format!( - "{} -- {} -- ", - self.title, file_path - ) - } - (false, Some(file_path), Some(file_blame)) => { - format!( - "{} -- {} -- {}", - self.title, - file_path, - file_blame.commit_id.get_short_string() - ) - } - (false, Some(file_path), None) => { - format!( - "{} -- {} -- ", - self.title, file_path - ) - } - _ => format!("{} -- ", self.title), - } - } + /// + fn get_title(&self) -> String { + match ( + self.any_work_pending(), + self.file_path.as_ref(), + self.file_blame.as_ref(), + ) { + (true, Some(file_path), _) => { + format!( + "{} -- {} -- ", + self.title, file_path + ) + } + (false, Some(file_path), Some(file_blame)) => { + format!( + "{} -- {} -- {}", + self.title, + file_path, + file_blame.commit_id.get_short_string() + ) + } + (false, Some(file_path), None) => { + format!( + "{} -- {} -- ", + self.title, file_path + ) + } + _ => format!("{} -- ", self.title), + } + } - /// - fn get_rows(&self, width: usize) -> Vec { - if let Some(ref file_blame) = self.file_blame { - file_blame - .lines - .iter() - .enumerate() - .map(|(i, (blame_hunk, line))| { - self.get_line_blame( - width, - i, - (blame_hunk.as_ref(), line.as_ref()), - file_blame, - ) - }) - .collect() - } else { - vec![] - } - } + /// + fn get_rows(&self, width: usize) -> Vec { + if let Some(ref file_blame) = self.file_blame { + file_blame + .lines + .iter() + .enumerate() + .map(|(i, (blame_hunk, line))| { + self.get_line_blame( + width, + i, + (blame_hunk.as_ref(), line.as_ref()), + file_blame, + ) + }) + .collect() + } else { + vec![] + } + } - fn get_line_blame( - &self, - width: usize, - line_number: usize, - hunk_and_line: (Option<&BlameHunk>, &str), - file_blame: &FileBlame, - ) -> Row { - let (hunk_for_line, line) = hunk_and_line; + fn get_line_blame( + &self, + width: usize, + line_number: usize, + hunk_and_line: (Option<&BlameHunk>, &str), + file_blame: &FileBlame, + ) -> Row { + let (hunk_for_line, line) = hunk_and_line; - let show_metadata = if line_number == 0 { - true - } else { - let hunk_for_previous_line = - &file_blame.lines[line_number - 1]; + let show_metadata = if line_number == 0 { + true + } else { + let hunk_for_previous_line = + &file_blame.lines[line_number - 1]; - match (hunk_for_previous_line, hunk_for_line) { - ((Some(previous), _), Some(current)) => { - previous.commit_id != current.commit_id - } - _ => true, - } - }; + match (hunk_for_previous_line, hunk_for_line) { + ((Some(previous), _), Some(current)) => { + previous.commit_id != current.commit_id + } + _ => true, + } + }; - let mut cells = if show_metadata { - self.get_metadata_for_line_blame(width, hunk_for_line) - } else { - vec![Cell::from(""), Cell::from(""), Cell::from("")] - }; + let mut cells = if show_metadata { + self.get_metadata_for_line_blame(width, hunk_for_line) + } else { + vec![Cell::from(""), Cell::from(""), Cell::from("")] + }; - let line_number_width = self.get_line_number_width(); - cells.push( - Cell::from(format!( - "{:>line_number_width$}{}", - line_number, - VERTICAL, - line_number_width = line_number_width, - )) - .style(self.theme.text(true, false)), - ); - cells.push( - Cell::from(String::from(line)) - .style(self.theme.text(true, false)), - ); + let line_number_width = self.get_line_number_width(); + cells.push( + Cell::from(format!( + "{:>line_number_width$}{}", + line_number, + VERTICAL, + line_number_width = line_number_width, + )) + .style(self.theme.text(true, false)), + ); + cells.push( + Cell::from(String::from(line)) + .style(self.theme.text(true, false)), + ); - Row::new(cells) - } + Row::new(cells) + } - fn get_metadata_for_line_blame( - &self, - width: usize, - blame_hunk: Option<&BlameHunk>, - ) -> Vec { - let commit_hash = blame_hunk.map_or_else( - || NO_COMMIT_ID.into(), - |hunk| hunk.commit_id.get_short_string(), - ); - let author_width = get_author_width(width); - let truncated_author: String = blame_hunk.map_or_else( - || NO_AUTHOR.into(), - |hunk| string_width_align(&hunk.author, author_width), - ); - let author = format!( - "{:author_width$}", - truncated_author, - author_width = MAX_AUTHOR_WIDTH - ); - let time = blame_hunk.map_or_else( - || "".into(), - |hunk| utils::time_to_string(hunk.time, true), - ); + fn get_metadata_for_line_blame( + &self, + width: usize, + blame_hunk: Option<&BlameHunk>, + ) -> Vec { + let commit_hash = blame_hunk.map_or_else( + || NO_COMMIT_ID.into(), + |hunk| hunk.commit_id.get_short_string(), + ); + let author_width = get_author_width(width); + let truncated_author: String = blame_hunk.map_or_else( + || NO_AUTHOR.into(), + |hunk| string_width_align(&hunk.author, author_width), + ); + let author = format!( + "{:author_width$}", + truncated_author, + author_width = MAX_AUTHOR_WIDTH + ); + let time = blame_hunk.map_or_else( + || "".into(), + |hunk| utils::time_to_string(hunk.time, true), + ); - let is_blamed_commit = self - .file_blame - .as_ref() - .and_then(|file_blame| { - blame_hunk.map(|hunk| { - file_blame.commit_id == hunk.commit_id - }) - }) - .unwrap_or(false); + let is_blamed_commit = self + .file_blame + .as_ref() + .and_then(|file_blame| { + blame_hunk.map(|hunk| { + file_blame.commit_id == hunk.commit_id + }) + }) + .unwrap_or(false); - vec![ - Cell::from(commit_hash).style( - self.theme.commit_hash_in_blame(is_blamed_commit), - ), - Cell::from(time).style(self.theme.commit_time(false)), - Cell::from(author).style(self.theme.commit_author(false)), - ] - } + vec![ + Cell::from(commit_hash).style( + self.theme.commit_hash_in_blame(is_blamed_commit), + ), + Cell::from(time).style(self.theme.commit_time(false)), + Cell::from(author).style(self.theme.commit_author(false)), + ] + } - fn get_max_line_number(&self) -> usize { - self.file_blame - .as_ref() - .map_or(0, |file_blame| file_blame.lines.len() - 1) - } + fn get_max_line_number(&self) -> usize { + self.file_blame + .as_ref() + .map_or(0, |file_blame| file_blame.lines.len() - 1) + } - fn get_line_number_width(&self) -> usize { - let max_line_number = self.get_max_line_number(); + fn get_line_number_width(&self) -> usize { + let max_line_number = self.get_max_line_number(); - number_of_digits(max_line_number) - } + number_of_digits(max_line_number) + } - fn move_selection(&mut self, scroll_type: ScrollType) -> bool { - let mut table_state = self.table_state.take(); + fn move_selection(&mut self, scroll_type: ScrollType) -> bool { + let mut table_state = self.table_state.take(); - let old_selection = table_state.selected().unwrap_or(0); - let max_selection = self.get_max_line_number(); + let old_selection = table_state.selected().unwrap_or(0); + let max_selection = self.get_max_line_number(); - let new_selection = match scroll_type { - ScrollType::Up => old_selection.saturating_sub(1), - ScrollType::Down => { - old_selection.saturating_add(1).min(max_selection) - } - ScrollType::Home => 0, - ScrollType::End => max_selection, - ScrollType::PageUp => old_selection.saturating_sub( - self.current_height.get().saturating_sub(2), - ), - ScrollType::PageDown => old_selection - .saturating_add( - self.current_height.get().saturating_sub(2), - ) - .min(max_selection), - }; + let new_selection = match scroll_type { + ScrollType::Up => old_selection.saturating_sub(1), + ScrollType::Down => { + old_selection.saturating_add(1).min(max_selection) + } + ScrollType::Home => 0, + ScrollType::End => max_selection, + ScrollType::PageUp => old_selection.saturating_sub( + self.current_height.get().saturating_sub(2), + ), + ScrollType::PageDown => old_selection + .saturating_add( + self.current_height.get().saturating_sub(2), + ) + .min(max_selection), + }; - let needs_update = new_selection != old_selection; + let needs_update = new_selection != old_selection; - table_state.select(Some(new_selection)); - self.table_state.set(table_state); + table_state.select(Some(new_selection)); + self.table_state.set(table_state); - needs_update - } + needs_update + } - fn selected_commit(&self) -> Option { - self.file_blame.as_ref().and_then(|file_blame| { - let table_state = self.table_state.take(); + fn selected_commit(&self) -> Option { + self.file_blame.as_ref().and_then(|file_blame| { + let table_state = self.table_state.take(); - let commit_id = - table_state.selected().and_then(|selected| { - file_blame.lines[selected] - .0 - .as_ref() - .map(|hunk| hunk.commit_id) - }); + let commit_id = + table_state.selected().and_then(|selected| { + file_blame.lines[selected] + .0 + .as_ref() + .map(|hunk| hunk.commit_id) + }); - self.table_state.set(table_state); + self.table_state.set(table_state); - commit_id - }) - } + commit_id + }) + } } diff --git a/src/components/branchlist.rs b/src/components/branchlist.rs index 0c144b26..b183cace 100644 --- a/src/components/branchlist.rs +++ b/src/components/branchlist.rs @@ -1,581 +1,580 @@ use super::{ - utils::scroll_vertical::VerticalScroll, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + utils::scroll_vertical::VerticalScroll, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - components::ScrollType, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, NeedsUpdate, Queue}, - strings, try_or_popup, - ui::{self, Size}, + components::ScrollType, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, NeedsUpdate, Queue}, + strings, try_or_popup, + ui::{self, Size}, }; use anyhow::Result; use asyncgit::{ - sync::{ - self, branch::checkout_remote_branch, checkout_branch, - get_branches_info, BranchInfo, - }, - AsyncGitNotification, CWD, + sync::{ + self, branch::checkout_remote_branch, checkout_branch, + get_branches_info, BranchInfo, + }, + AsyncGitNotification, CWD, }; use crossterm::event::Event; use std::{cell::Cell, convert::TryInto}; use tui::{ - backend::Backend, - layout::{ - Alignment, Constraint, Direction, Layout, Margin, Rect, - }, - text::{Span, Spans, Text}, - widgets::{Block, BorderType, Borders, Clear, Paragraph, Tabs}, - Frame, + backend::Backend, + layout::{ + Alignment, Constraint, Direction, Layout, Margin, Rect, + }, + text::{Span, Spans, Text}, + widgets::{Block, BorderType, Borders, Clear, Paragraph, Tabs}, + Frame, }; use ui::style::SharedTheme; use unicode_truncate::UnicodeTruncateStr; /// pub struct BranchListComponent { - branches: Vec, - local: bool, - visible: bool, - selection: u16, - scroll: VerticalScroll, - current_height: Cell, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, + branches: Vec, + local: bool, + visible: bool, + selection: u16, + scroll: VerticalScroll, + current_height: Cell, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, } impl DrawableComponent for BranchListComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.is_visible() { - const PERCENT_SIZE: Size = Size::new(80, 50); - const MIN_SIZE: Size = Size::new(60, 20); + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.is_visible() { + const PERCENT_SIZE: Size = Size::new(80, 50); + const MIN_SIZE: Size = Size::new(60, 20); - let area = ui::centered_rect( - PERCENT_SIZE.width, - PERCENT_SIZE.height, - f.size(), - ); - let area = - ui::rect_inside(MIN_SIZE, f.size().into(), area); - let area = area.intersection(rect); + let area = ui::centered_rect( + PERCENT_SIZE.width, + PERCENT_SIZE.height, + f.size(), + ); + let area = + ui::rect_inside(MIN_SIZE, f.size().into(), area); + let area = area.intersection(rect); - f.render_widget(Clear, area); + f.render_widget(Clear, area); - f.render_widget( - Block::default() - .title(strings::title_branches()) - .border_type(BorderType::Thick) - .borders(Borders::ALL), - area, - ); + f.render_widget( + Block::default() + .title(strings::title_branches()) + .border_type(BorderType::Thick) + .borders(Borders::ALL), + area, + ); - let area = area.inner(&Margin { - vertical: 1, - horizontal: 1, - }); + let area = area.inner(&Margin { + vertical: 1, + horizontal: 1, + }); - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [Constraint::Length(2), Constraint::Min(1)] - .as_ref(), - ) - .split(area); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [Constraint::Length(2), Constraint::Min(1)] + .as_ref(), + ) + .split(area); - self.draw_tabs(f, chunks[0]); - self.draw_list(f, chunks[1])?; - } + self.draw_tabs(f, chunks[0]); + self.draw_list(f, chunks[1])?; + } - Ok(()) - } + Ok(()) + } } impl Component for BranchListComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - if !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + if !force_all { + out.clear(); + } - out.push(CommandInfo::new( - strings::commands::scroll(&self.key_config), - true, - true, - )); + out.push(CommandInfo::new( + strings::commands::scroll(&self.key_config), + true, + true, + )); - out.push(CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - )); + out.push(CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + )); - out.push(CommandInfo::new( - strings::commands::toggle_branch_popup( - &self.key_config, - self.local, - ), - true, - true, - )); + out.push(CommandInfo::new( + strings::commands::toggle_branch_popup( + &self.key_config, + self.local, + ), + true, + true, + )); - out.push(CommandInfo::new( - strings::commands::select_branch_popup( - &self.key_config, - ), - !self.selection_is_cur_branch() - && self.valid_selection(), - true, - )); + out.push(CommandInfo::new( + strings::commands::select_branch_popup( + &self.key_config, + ), + !self.selection_is_cur_branch() + && self.valid_selection(), + true, + )); - out.push(CommandInfo::new( - strings::commands::open_branch_create_popup( - &self.key_config, - ), - true, - self.local, - )); + out.push(CommandInfo::new( + strings::commands::open_branch_create_popup( + &self.key_config, + ), + true, + self.local, + )); - out.push(CommandInfo::new( - strings::commands::delete_branch_popup( - &self.key_config, - ), - !self.selection_is_cur_branch(), - true, - )); + out.push(CommandInfo::new( + strings::commands::delete_branch_popup( + &self.key_config, + ), + !self.selection_is_cur_branch(), + true, + )); - out.push(CommandInfo::new( - strings::commands::merge_branch_popup( - &self.key_config, - ), - !self.selection_is_cur_branch(), - self.local, - )); + out.push(CommandInfo::new( + strings::commands::merge_branch_popup( + &self.key_config, + ), + !self.selection_is_cur_branch(), + self.local, + )); - out.push(CommandInfo::new( - strings::commands::rename_branch_popup( - &self.key_config, - ), - true, - self.local, - )); - } - visibility_blocking(self) - } + out.push(CommandInfo::new( + strings::commands::rename_branch_popup( + &self.key_config, + ), + true, + self.local, + )); + } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - } else if e == self.key_config.move_down { - return self - .move_selection(ScrollType::Up) - .map(Into::into); - } else if e == self.key_config.move_up { - return self - .move_selection(ScrollType::Down) - .map(Into::into); - } else if e == self.key_config.page_down { - return self - .move_selection(ScrollType::PageDown) - .map(Into::into); - } else if e == self.key_config.page_up { - return self - .move_selection(ScrollType::PageUp) - .map(Into::into); - } else if e == self.key_config.enter { - try_or_popup!( - self, - "switch branch error:", - self.switch_to_selected_branch() - ); - } else if e == self.key_config.create_branch - && self.local - { - self.queue.push(InternalEvent::CreateBranch); - } else if e == self.key_config.rename_branch - && self.valid_selection() - { - let cur_branch = - &self.branches[self.selection as usize]; - self.queue.push(InternalEvent::RenameBranch( - cur_branch.reference.clone(), - cur_branch.name.clone(), - )); + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + } else if e == self.key_config.move_down { + return self + .move_selection(ScrollType::Up) + .map(Into::into); + } else if e == self.key_config.move_up { + return self + .move_selection(ScrollType::Down) + .map(Into::into); + } else if e == self.key_config.page_down { + return self + .move_selection(ScrollType::PageDown) + .map(Into::into); + } else if e == self.key_config.page_up { + return self + .move_selection(ScrollType::PageUp) + .map(Into::into); + } else if e == self.key_config.enter { + try_or_popup!( + self, + "switch branch error:", + self.switch_to_selected_branch() + ); + } else if e == self.key_config.create_branch + && self.local + { + self.queue.push(InternalEvent::CreateBranch); + } else if e == self.key_config.rename_branch + && self.valid_selection() + { + let cur_branch = + &self.branches[self.selection as usize]; + self.queue.push(InternalEvent::RenameBranch( + cur_branch.reference.clone(), + cur_branch.name.clone(), + )); - self.update_branches()?; - } else if e == self.key_config.delete_branch - && !self.selection_is_cur_branch() - && self.valid_selection() - { - self.queue.push(InternalEvent::ConfirmAction( - Action::DeleteBranch( - self.branches[self.selection as usize] - .reference - .clone(), - self.local, - ), - )); - } else if e == self.key_config.merge_branch - && !self.selection_is_cur_branch() - && self.valid_selection() - { - try_or_popup!( - self, - "merge branch error:", - self.merge_branch() - ); - self.hide(); - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - } else if e == self.key_config.tab_toggle { - self.local = !self.local; - self.update_branches()?; - } - } + self.update_branches()?; + } else if e == self.key_config.delete_branch + && !self.selection_is_cur_branch() + && self.valid_selection() + { + self.queue.push(InternalEvent::ConfirmAction( + Action::DeleteBranch( + self.branches[self.selection as usize] + .reference + .clone(), + self.local, + ), + )); + } else if e == self.key_config.merge_branch + && !self.selection_is_cur_branch() + && self.valid_selection() + { + try_or_popup!( + self, + "merge branch error:", + self.merge_branch() + ); + self.hide(); + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); + } else if e == self.key_config.tab_toggle { + self.local = !self.local; + self.update_branches()?; + } + } - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - } - } + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + } + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } impl BranchListComponent { - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - branches: Vec::new(), - local: true, - visible: false, - selection: 0, - scroll: VerticalScroll::new(), - queue, - theme, - key_config, - current_height: Cell::new(0), - } - } + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + branches: Vec::new(), + local: true, + visible: false, + selection: 0, + scroll: VerticalScroll::new(), + queue, + theme, + key_config, + current_height: Cell::new(0), + } + } - /// - pub fn open(&mut self) -> Result<()> { - self.show()?; - self.update_branches()?; + /// + pub fn open(&mut self) -> Result<()> { + self.show()?; + self.update_branches()?; - Ok(()) - } + Ok(()) + } - /// fetch list of branches - pub fn update_branches(&mut self) -> Result<()> { - if self.is_visible() { - self.branches = get_branches_info(CWD, self.local)?; - //remove remote branch called `HEAD` - if !self.local { - self.branches - .iter() - .position(|b| b.name.ends_with("/HEAD")) - .map(|idx| self.branches.remove(idx)); - } - self.set_selection(self.selection)?; - } - Ok(()) - } + /// fetch list of branches + pub fn update_branches(&mut self) -> Result<()> { + if self.is_visible() { + self.branches = get_branches_info(CWD, self.local)?; + //remove remote branch called `HEAD` + if !self.local { + self.branches + .iter() + .position(|b| b.name.ends_with("/HEAD")) + .map(|idx| self.branches.remove(idx)); + } + self.set_selection(self.selection)?; + } + Ok(()) + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::Push = ev { - self.update_branches()?; - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::Push = ev { + self.update_branches()?; + } + } - Ok(()) - } + Ok(()) + } - fn valid_selection(&self) -> bool { - !self.branches.is_empty() - } + fn valid_selection(&self) -> bool { + !self.branches.is_empty() + } - fn merge_branch(&self) -> Result<()> { - if let Some(branch) = - self.branches.get(usize::from(self.selection)) - { - sync::merge_branch(CWD, &branch.name)?; - } + fn merge_branch(&self) -> Result<()> { + if let Some(branch) = + self.branches.get(usize::from(self.selection)) + { + sync::merge_branch(CWD, &branch.name)?; + } - Ok(()) - } + Ok(()) + } - fn selection_is_cur_branch(&self) -> bool { - self.branches - .iter() - .enumerate() - .filter(|(index, b)| { - b.local_details() - .map(|details| { - details.is_head - && *index == self.selection as usize - }) - .unwrap_or_default() - }) - .count() - > 0 - } + fn selection_is_cur_branch(&self) -> bool { + self.branches + .iter() + .enumerate() + .filter(|(index, b)| { + b.local_details() + .map(|details| { + details.is_head + && *index == self.selection as usize + }) + .unwrap_or_default() + }) + .count() > 0 + } - /// - fn move_selection(&mut self, scroll: ScrollType) -> Result { - let new_selection = match scroll { - ScrollType::Up => self.selection.saturating_add(1), - ScrollType::Down => self.selection.saturating_sub(1), - ScrollType::PageDown => self - .selection - .saturating_add(self.current_height.get()), - ScrollType::PageUp => self - .selection - .saturating_sub(self.current_height.get()), - _ => self.selection, - }; + /// + fn move_selection(&mut self, scroll: ScrollType) -> Result { + let new_selection = match scroll { + ScrollType::Up => self.selection.saturating_add(1), + ScrollType::Down => self.selection.saturating_sub(1), + ScrollType::PageDown => self + .selection + .saturating_add(self.current_height.get()), + ScrollType::PageUp => self + .selection + .saturating_sub(self.current_height.get()), + _ => self.selection, + }; - self.set_selection(new_selection)?; + self.set_selection(new_selection)?; - Ok(true) - } + Ok(true) + } - fn set_selection(&mut self, selection: u16) -> Result<()> { - let num_branches: u16 = self.branches.len().try_into()?; - let num_branches = num_branches.saturating_sub(1); + fn set_selection(&mut self, selection: u16) -> Result<()> { + let num_branches: u16 = self.branches.len().try_into()?; + let num_branches = num_branches.saturating_sub(1); - let selection = if selection > num_branches { - num_branches - } else { - selection - }; + let selection = if selection > num_branches { + num_branches + } else { + selection + }; - self.selection = selection; + self.selection = selection; - Ok(()) - } + Ok(()) + } - /// Get branches to display - fn get_text( - &self, - theme: &SharedTheme, - width_available: u16, - height: usize, - ) -> Text { - const UPSTREAM_SYMBOL: char = '\u{2191}'; - const HEAD_SYMBOL: char = '*'; - const EMPTY_SYMBOL: char = ' '; - const THREE_DOTS: &str = "..."; - const COMMIT_HASH_LENGTH: usize = 8; - const IS_HEAD_STAR_LENGTH: usize = 3; // "* " - const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..." + /// Get branches to display + fn get_text( + &self, + theme: &SharedTheme, + width_available: u16, + height: usize, + ) -> Text { + const UPSTREAM_SYMBOL: char = '\u{2191}'; + const HEAD_SYMBOL: char = '*'; + const EMPTY_SYMBOL: char = ' '; + const THREE_DOTS: &str = "..."; + const COMMIT_HASH_LENGTH: usize = 8; + const IS_HEAD_STAR_LENGTH: usize = 3; // "* " + const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..." - let branch_name_length: usize = - width_available as usize * 40 / 100; - // commit message takes up the remaining width - let commit_message_length: usize = (width_available as usize) - .saturating_sub(COMMIT_HASH_LENGTH) - .saturating_sub(branch_name_length) - .saturating_sub(IS_HEAD_STAR_LENGTH) - .saturating_sub(THREE_DOTS_LENGTH); - let mut txt = Vec::new(); + let branch_name_length: usize = + width_available as usize * 40 / 100; + // commit message takes up the remaining width + let commit_message_length: usize = (width_available as usize) + .saturating_sub(COMMIT_HASH_LENGTH) + .saturating_sub(branch_name_length) + .saturating_sub(IS_HEAD_STAR_LENGTH) + .saturating_sub(THREE_DOTS_LENGTH); + let mut txt = Vec::new(); - for (i, displaybranch) in self - .branches - .iter() - .skip(self.scroll.get_top()) - .take(height) - .enumerate() - { - let mut commit_message = - displaybranch.top_commit_message.clone(); - if commit_message.len() > commit_message_length { - commit_message.unicode_truncate( - commit_message_length - .saturating_sub(THREE_DOTS_LENGTH), - ); - commit_message += THREE_DOTS; - } + for (i, displaybranch) in self + .branches + .iter() + .skip(self.scroll.get_top()) + .take(height) + .enumerate() + { + let mut commit_message = + displaybranch.top_commit_message.clone(); + if commit_message.len() > commit_message_length { + commit_message.unicode_truncate( + commit_message_length + .saturating_sub(THREE_DOTS_LENGTH), + ); + commit_message += THREE_DOTS; + } - let mut branch_name = displaybranch.name.clone(); - if branch_name.len() - > branch_name_length.saturating_sub(THREE_DOTS_LENGTH) - { - branch_name = branch_name - .unicode_truncate( - branch_name_length - .saturating_sub(THREE_DOTS_LENGTH), - ) - .0 - .to_string(); - branch_name += THREE_DOTS; - } + let mut branch_name = displaybranch.name.clone(); + if branch_name.len() + > branch_name_length.saturating_sub(THREE_DOTS_LENGTH) + { + branch_name = branch_name + .unicode_truncate( + branch_name_length + .saturating_sub(THREE_DOTS_LENGTH), + ) + .0 + .to_string(); + branch_name += THREE_DOTS; + } - let selected = (self.selection as usize - - self.scroll.get_top()) - == i; + let selected = (self.selection as usize + - self.scroll.get_top()) + == i; - let is_head = displaybranch - .local_details() - .map(|details| details.is_head) - .unwrap_or_default(); - let is_head_str = - if is_head { HEAD_SYMBOL } else { EMPTY_SYMBOL }; - let has_upstream_str = if displaybranch - .local_details() - .map(|details| details.has_upstream) - .unwrap_or_default() - { - UPSTREAM_SYMBOL - } else { - EMPTY_SYMBOL - }; + let is_head = displaybranch + .local_details() + .map(|details| details.is_head) + .unwrap_or_default(); + let is_head_str = + if is_head { HEAD_SYMBOL } else { EMPTY_SYMBOL }; + let has_upstream_str = if displaybranch + .local_details() + .map(|details| details.has_upstream) + .unwrap_or_default() + { + UPSTREAM_SYMBOL + } else { + EMPTY_SYMBOL + }; - let span_prefix = Span::styled( - format!("{}{} ", is_head_str, has_upstream_str), - theme.commit_author(selected), - ); - let span_hash = Span::styled( - format!( - "{} ", - displaybranch.top_commit.get_short_string() - ), - theme.commit_hash(selected), - ); - let span_msg = Span::styled( - commit_message.to_string(), - theme.text(true, selected), - ); - let span_name = Span::styled( - format!( - "{:w$} ", - branch_name, - w = branch_name_length - ), - theme.branch(selected, is_head), - ); + let span_prefix = Span::styled( + format!("{}{} ", is_head_str, has_upstream_str), + theme.commit_author(selected), + ); + let span_hash = Span::styled( + format!( + "{} ", + displaybranch.top_commit.get_short_string() + ), + theme.commit_hash(selected), + ); + let span_msg = Span::styled( + commit_message.to_string(), + theme.text(true, selected), + ); + let span_name = Span::styled( + format!( + "{:w$} ", + branch_name, + w = branch_name_length + ), + theme.branch(selected, is_head), + ); - txt.push(Spans::from(vec![ - span_prefix, - span_name, - span_hash, - span_msg, - ])); - } + txt.push(Spans::from(vec![ + span_prefix, + span_name, + span_hash, + span_msg, + ])); + } - Text::from(txt) - } + Text::from(txt) + } - /// - fn switch_to_selected_branch(&mut self) -> Result<()> { - if !self.valid_selection() { - anyhow::bail!("no valid branch selected"); - } + /// + fn switch_to_selected_branch(&mut self) -> Result<()> { + if !self.valid_selection() { + anyhow::bail!("no valid branch selected"); + } - if self.local { - checkout_branch( - asyncgit::CWD, - &self.branches[self.selection as usize].reference, - )?; - self.hide(); - } else { - checkout_remote_branch( - CWD, - &self.branches[self.selection as usize], - )?; - self.local = true; - self.update_branches()?; - } + if self.local { + checkout_branch( + asyncgit::CWD, + &self.branches[self.selection as usize].reference, + )?; + self.hide(); + } else { + checkout_remote_branch( + CWD, + &self.branches[self.selection as usize], + )?; + self.local = true; + self.update_branches()?; + } - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - Ok(()) - } + Ok(()) + } - fn draw_tabs(&self, f: &mut Frame, r: Rect) { - let tabs = [Span::raw("Local"), Span::raw("Remote")] - .iter() - .cloned() - .map(Spans::from) - .collect(); + fn draw_tabs(&self, f: &mut Frame, r: Rect) { + let tabs = [Span::raw("Local"), Span::raw("Remote")] + .iter() + .cloned() + .map(Spans::from) + .collect(); - f.render_widget( - Tabs::new(tabs) - .block( - Block::default() - .borders(Borders::BOTTOM) - .border_style(self.theme.block(false)), - ) - .style(self.theme.tab(false)) - .highlight_style(self.theme.tab(true)) - .divider(strings::tab_divider(&self.key_config)) - .select(if self.local { 0 } else { 1 }), - r, - ); - } + f.render_widget( + Tabs::new(tabs) + .block( + Block::default() + .borders(Borders::BOTTOM) + .border_style(self.theme.block(false)), + ) + .style(self.theme.tab(false)) + .highlight_style(self.theme.tab(true)) + .divider(strings::tab_divider(&self.key_config)) + .select(if self.local { 0 } else { 1 }), + r, + ); + } - fn draw_list( - &self, - f: &mut Frame, - r: Rect, - ) -> Result<()> { - let height_in_lines = r.height as usize; - self.current_height.set(height_in_lines.try_into()?); + fn draw_list( + &self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { + let height_in_lines = r.height as usize; + self.current_height.set(height_in_lines.try_into()?); - self.scroll.update( - self.selection as usize, - self.branches.len(), - height_in_lines, - ); + self.scroll.update( + self.selection as usize, + self.branches.len(), + height_in_lines, + ); - f.render_widget( - Paragraph::new(self.get_text( - &self.theme, - r.width, - height_in_lines, - )) - .alignment(Alignment::Left), - r, - ); + f.render_widget( + Paragraph::new(self.get_text( + &self.theme, + r.width, + height_in_lines, + )) + .alignment(Alignment::Left), + r, + ); - let mut r = r; - r.width += 1; - r.height += 2; - r.y = r.y.saturating_sub(1); + let mut r = r; + r.width += 1; + r.height += 2; + r.y = r.y.saturating_sub(1); - self.scroll.draw(f, r, &self.theme); + self.scroll.draw(f, r, &self.theme); - Ok(()) - } + Ok(()) + } } diff --git a/src/components/changes.rs b/src/components/changes.rs index c50d3b24..42ece875 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -1,14 +1,14 @@ use super::{ - filetree::FileTreeComponent, - utils::filetree::{FileTreeItem, FileTreeItemKind}, - CommandBlocking, DrawableComponent, + filetree::FileTreeComponent, + utils::filetree::{FileTreeItem, FileTreeItemKind}, + CommandBlocking, DrawableComponent, }; use crate::{ - components::{CommandInfo, Component, EventState}, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, - strings, try_or_popup, - ui::style::SharedTheme, + components::{CommandInfo, Component, EventState}, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, + strings, try_or_popup, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{sync, StatusItem, StatusItemType, CWD}; @@ -18,283 +18,283 @@ use tui::{backend::Backend, layout::Rect, Frame}; /// pub struct ChangesComponent { - files: FileTreeComponent, - is_working_dir: bool, - queue: Queue, - key_config: SharedKeyConfig, + files: FileTreeComponent, + is_working_dir: bool, + queue: Queue, + key_config: SharedKeyConfig, } impl ChangesComponent { - /// - pub fn new( - title: &str, - focus: bool, - is_working_dir: bool, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - files: FileTreeComponent::new( - title, - focus, - Some(queue.clone()), - theme, - key_config.clone(), - ), - is_working_dir, - queue, - key_config, - } - } + /// + pub fn new( + title: &str, + focus: bool, + is_working_dir: bool, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + files: FileTreeComponent::new( + title, + focus, + Some(queue.clone()), + theme, + key_config.clone(), + ), + is_working_dir, + queue, + key_config, + } + } - /// - pub fn set_items(&mut self, list: &[StatusItem]) -> Result<()> { - self.files.update(list)?; - Ok(()) - } + /// + pub fn set_items(&mut self, list: &[StatusItem]) -> Result<()> { + self.files.update(list)?; + Ok(()) + } - /// - pub fn selection(&self) -> Option { - self.files.selection() - } + /// + pub fn selection(&self) -> Option { + self.files.selection() + } - /// - pub fn focus_select(&mut self, focus: bool) { - self.files.focus(focus); - self.files.show_selection(focus); - } + /// + pub fn focus_select(&mut self, focus: bool) { + self.files.focus(focus); + self.files.show_selection(focus); + } - /// returns true if list is empty - pub fn is_empty(&self) -> bool { - self.files.is_empty() - } + /// returns true if list is empty + pub fn is_empty(&self) -> bool { + self.files.is_empty() + } - /// - pub fn is_file_seleted(&self) -> bool { - self.files.is_file_seleted() - } + /// + pub fn is_file_seleted(&self) -> bool { + self.files.is_file_seleted() + } - fn index_add_remove(&mut self) -> Result { - if let Some(tree_item) = self.selection() { - if self.is_working_dir { - if let FileTreeItemKind::File(i) = tree_item.kind { - let path = Path::new(i.path.as_str()); - match i.status { - StatusItemType::Deleted => { - sync::stage_addremoved(CWD, path)?; - } - _ => sync::stage_add_file(CWD, path)?, - }; + fn index_add_remove(&mut self) -> Result { + if let Some(tree_item) = self.selection() { + if self.is_working_dir { + if let FileTreeItemKind::File(i) = tree_item.kind { + let path = Path::new(i.path.as_str()); + match i.status { + StatusItemType::Deleted => { + sync::stage_addremoved(CWD, path)?; + } + _ => sync::stage_add_file(CWD, path)?, + }; - if self.is_empty() { - self.queue - .push(InternalEvent::StatusLastFileMoved); - } + if self.is_empty() { + self.queue + .push(InternalEvent::StatusLastFileMoved); + } - return Ok(true); - } + return Ok(true); + } - //TODO: check if we can handle the one file case with it aswell - sync::stage_add_all( - CWD, - tree_item.info.full_path.as_str(), - )?; + //TODO: check if we can handle the one file case with it aswell + sync::stage_add_all( + CWD, + tree_item.info.full_path.as_str(), + )?; - return Ok(true); - } + return Ok(true); + } - let path = tree_item.info.full_path.as_str(); - sync::reset_stage(CWD, path)?; - return Ok(true); - } + let path = tree_item.info.full_path.as_str(); + sync::reset_stage(CWD, path)?; + return Ok(true); + } - Ok(false) - } + Ok(false) + } - fn index_add_all(&mut self) -> Result<()> { - sync::stage_add_all(CWD, "*")?; + fn index_add_all(&mut self) -> Result<()> { + sync::stage_add_all(CWD, "*")?; - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - Ok(()) - } + Ok(()) + } - fn stage_remove_all(&mut self) -> Result<()> { - sync::reset_stage(CWD, "*")?; + fn stage_remove_all(&mut self) -> Result<()> { + sync::reset_stage(CWD, "*")?; - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - Ok(()) - } + Ok(()) + } - fn dispatch_reset_workdir(&mut self) -> bool { - if let Some(tree_item) = self.selection() { - let is_folder = - matches!(tree_item.kind, FileTreeItemKind::Path(_)); - self.queue.push(InternalEvent::ConfirmAction( - Action::Reset(ResetItem { - path: tree_item.info.full_path, - is_folder, - }), - )); + fn dispatch_reset_workdir(&mut self) -> bool { + if let Some(tree_item) = self.selection() { + let is_folder = + matches!(tree_item.kind, FileTreeItemKind::Path(_)); + self.queue.push(InternalEvent::ConfirmAction( + Action::Reset(ResetItem { + path: tree_item.info.full_path, + is_folder, + }), + )); - return true; - } - false - } + return true; + } + false + } - fn add_to_ignore(&mut self) -> bool { - if let Some(tree_item) = self.selection() { - if let Err(e) = - sync::add_to_ignore(CWD, &tree_item.info.full_path) - { - self.queue.push(InternalEvent::ShowErrorMsg( - format!( - "ignore error:\n{}\nfile:\n{:?}", - e, tree_item.info.full_path - ), - )); - } else { - self.queue - .push(InternalEvent::Update(NeedsUpdate::ALL)); + fn add_to_ignore(&mut self) -> bool { + if let Some(tree_item) = self.selection() { + if let Err(e) = + sync::add_to_ignore(CWD, &tree_item.info.full_path) + { + self.queue.push(InternalEvent::ShowErrorMsg( + format!( + "ignore error:\n{}\nfile:\n{:?}", + e, tree_item.info.full_path + ), + )); + } else { + self.queue + .push(InternalEvent::Update(NeedsUpdate::ALL)); - return true; - } - } + return true; + } + } - false - } + false + } } impl DrawableComponent for ChangesComponent { - fn draw( - &self, - f: &mut Frame, - r: Rect, - ) -> Result<()> { - self.files.draw(f, r)?; + fn draw( + &self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { + self.files.draw(f, r)?; - Ok(()) - } + Ok(()) + } } impl Component for ChangesComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - self.files.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + self.files.commands(out, force_all); - let some_selection = self.selection().is_some(); + let some_selection = self.selection().is_some(); - if self.is_working_dir { - out.push(CommandInfo::new( - strings::commands::stage_all(&self.key_config), - some_selection, - self.focused(), - )); - out.push(CommandInfo::new( - strings::commands::stage_item(&self.key_config), - some_selection, - self.focused(), - )); - out.push(CommandInfo::new( - strings::commands::reset_item(&self.key_config), - some_selection, - self.focused(), - )); - out.push(CommandInfo::new( - strings::commands::ignore_item(&self.key_config), - some_selection, - self.focused(), - )); - } else { - out.push(CommandInfo::new( - strings::commands::unstage_item(&self.key_config), - some_selection, - self.focused(), - )); - out.push(CommandInfo::new( - strings::commands::unstage_all(&self.key_config), - some_selection, - self.focused(), - )); - out.push( - CommandInfo::new( - strings::commands::commit_open(&self.key_config), - !self.is_empty(), - self.focused() || force_all, - ) - .order(-1), - ); - } + if self.is_working_dir { + out.push(CommandInfo::new( + strings::commands::stage_all(&self.key_config), + some_selection, + self.focused(), + )); + out.push(CommandInfo::new( + strings::commands::stage_item(&self.key_config), + some_selection, + self.focused(), + )); + out.push(CommandInfo::new( + strings::commands::reset_item(&self.key_config), + some_selection, + self.focused(), + )); + out.push(CommandInfo::new( + strings::commands::ignore_item(&self.key_config), + some_selection, + self.focused(), + )); + } else { + out.push(CommandInfo::new( + strings::commands::unstage_item(&self.key_config), + some_selection, + self.focused(), + )); + out.push(CommandInfo::new( + strings::commands::unstage_all(&self.key_config), + some_selection, + self.focused(), + )); + out.push( + CommandInfo::new( + strings::commands::commit_open(&self.key_config), + !self.is_empty(), + self.focused() || force_all, + ) + .order(-1), + ); + } - CommandBlocking::PassingOn - } + CommandBlocking::PassingOn + } - fn event(&mut self, ev: Event) -> Result { - if self.files.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.files.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if self.focused() { - if let Event::Key(e) = ev { - return if e == self.key_config.open_commit - && !self.is_working_dir - && !self.is_empty() - { - self.queue.push(InternalEvent::OpenCommit); - Ok(EventState::Consumed) - } else if e == self.key_config.enter { - try_or_popup!( - self, - "staging error:", - self.index_add_remove() - ); + if self.focused() { + if let Event::Key(e) = ev { + return if e == self.key_config.open_commit + && !self.is_working_dir + && !self.is_empty() + { + self.queue.push(InternalEvent::OpenCommit); + Ok(EventState::Consumed) + } else if e == self.key_config.enter { + try_or_popup!( + self, + "staging error:", + self.index_add_remove() + ); - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - Ok(EventState::Consumed) - } else if e == self.key_config.status_stage_all - && !self.is_empty() - { - if self.is_working_dir { - try_or_popup!( - self, - "staging all error:", - self.index_add_all() - ); - } else { - self.stage_remove_all()?; - } - self.queue - .push(InternalEvent::StatusLastFileMoved); - Ok(EventState::Consumed) - } else if e == self.key_config.status_reset_item - && self.is_working_dir - { - Ok(self.dispatch_reset_workdir().into()) - } else if e == self.key_config.status_ignore_file - && self.is_working_dir - && !self.is_empty() - { - Ok(self.add_to_ignore().into()) - } else { - Ok(EventState::NotConsumed) - }; - } - } + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); + Ok(EventState::Consumed) + } else if e == self.key_config.status_stage_all + && !self.is_empty() + { + if self.is_working_dir { + try_or_popup!( + self, + "staging all error:", + self.index_add_all() + ); + } else { + self.stage_remove_all()?; + } + self.queue + .push(InternalEvent::StatusLastFileMoved); + Ok(EventState::Consumed) + } else if e == self.key_config.status_reset_item + && self.is_working_dir + { + Ok(self.dispatch_reset_workdir().into()) + } else if e == self.key_config.status_ignore_file + && self.is_working_dir + && !self.is_empty() + { + Ok(self.add_to_ignore().into()) + } else { + Ok(EventState::NotConsumed) + }; + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn focused(&self) -> bool { - self.files.focused() - } - fn focus(&mut self, focus: bool) { - self.files.focus(focus); - } + fn focused(&self) -> bool { + self.files.focused() + } + fn focus(&mut self, focus: bool) { + self.files.focus(focus); + } } diff --git a/src/components/command.rs b/src/components/command.rs index 8b6340f5..182069c5 100644 --- a/src/components/command.rs +++ b/src/components/command.rs @@ -1,85 +1,85 @@ /// #[derive(Clone, PartialEq, PartialOrd, Ord, Eq)] pub struct CommandText { - /// - pub name: String, - /// - pub desc: &'static str, - /// - pub group: &'static str, - /// - pub hide_help: bool, + /// + pub name: String, + /// + pub desc: &'static str, + /// + pub group: &'static str, + /// + pub hide_help: bool, } impl CommandText { - /// - pub const fn new( - name: String, - desc: &'static str, - group: &'static str, - ) -> Self { - Self { - name, - desc, - group, - hide_help: false, - } - } - /// - pub const fn hide_help(self) -> Self { - let mut tmp = self; - tmp.hide_help = true; - tmp - } + /// + pub const fn new( + name: String, + desc: &'static str, + group: &'static str, + ) -> Self { + Self { + name, + desc, + group, + hide_help: false, + } + } + /// + pub const fn hide_help(self) -> Self { + let mut tmp = self; + tmp.hide_help = true; + tmp + } } /// pub struct CommandInfo { - /// - pub text: CommandText, - /// available but not active in the context - pub enabled: bool, - /// will show up in the quick bar - pub quick_bar: bool, + /// + pub text: CommandText, + /// available but not active in the context + pub enabled: bool, + /// will show up in the quick bar + pub quick_bar: bool, - /// available in current app state - pub available: bool, - /// used to order commands in quickbar - pub order: i8, + /// available in current app state + pub available: bool, + /// used to order commands in quickbar + pub order: i8, } impl CommandInfo { - /// - pub const fn new( - text: CommandText, - enabled: bool, - available: bool, - ) -> Self { - Self { - text, - enabled, - quick_bar: true, - available, - order: 0, - } - } + /// + pub const fn new( + text: CommandText, + enabled: bool, + available: bool, + ) -> Self { + Self { + text, + enabled, + quick_bar: true, + available, + order: 0, + } + } - /// - pub const fn order(self, order: i8) -> Self { - let mut res = self; - res.order = order; - res - } + /// + pub const fn order(self, order: i8) -> Self { + let mut res = self; + res.order = order; + res + } - /// - pub const fn hidden(self) -> Self { - let mut res = self; - res.quick_bar = false; - res - } + /// + pub const fn hidden(self) -> Self { + let mut res = self; + res.quick_bar = false; + res + } - /// - pub const fn show_in_quickbar(&self) -> bool { - self.quick_bar && self.available - } + /// + pub const fn show_in_quickbar(&self) -> bool { + self.quick_bar && self.available + } } diff --git a/src/components/commit.rs b/src/components/commit.rs index 1b7788bd..61a30abf 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -1,398 +1,398 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, ExternalEditorComponent, + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, ExternalEditorComponent, }; use crate::{ - keys::SharedKeyConfig, - queue::{InternalEvent, NeedsUpdate, Queue}, - strings, try_or_popup, - ui::style::SharedTheme, + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings, try_or_popup, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - cached, - sync::{ - self, get_config_string, CommitId, HookResult, RepoState, - }, - CWD, + cached, + sync::{ + self, get_config_string, CommitId, HookResult, RepoState, + }, + CWD, }; use crossterm::event::Event; use easy_cast::Cast; use std::{ - fs::{read_to_string, File}, - io::{Read, Write}, + fs::{read_to_string, File}, + io::{Read, Write}, }; use tui::{ - backend::Backend, - layout::{Alignment, Rect}, - widgets::Paragraph, - Frame, + backend::Backend, + layout::{Alignment, Rect}, + widgets::Paragraph, + Frame, }; enum Mode { - Normal, - Amend(CommitId), - Merge(Vec), + Normal, + Amend(CommitId), + Merge(Vec), } pub struct CommitComponent { - input: TextInputComponent, - mode: Mode, - queue: Queue, - key_config: SharedKeyConfig, - git_branch_name: cached::BranchName, - commit_template: Option, - theme: SharedTheme, + input: TextInputComponent, + mode: Mode, + queue: Queue, + key_config: SharedKeyConfig, + git_branch_name: cached::BranchName, + commit_template: Option, + theme: SharedTheme, } const FIRST_LINE_LIMIT: usize = 50; impl CommitComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue, - mode: Mode::Normal, + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue, + mode: Mode::Normal, - input: TextInputComponent::new( - theme.clone(), - key_config.clone(), - "", - &strings::commit_msg(&key_config), - true, - ), - key_config, - git_branch_name: cached::BranchName::new(CWD), - commit_template: None, - theme, - } - } + input: TextInputComponent::new( + theme.clone(), + key_config.clone(), + "", + &strings::commit_msg(&key_config), + true, + ), + key_config, + git_branch_name: cached::BranchName::new(CWD), + commit_template: None, + theme, + } + } - /// - pub fn update(&mut self) { - self.git_branch_name.lookup().ok(); - } + /// + pub fn update(&mut self) { + self.git_branch_name.lookup().ok(); + } - fn draw_branch_name(&self, f: &mut Frame) { - if let Some(name) = self.git_branch_name.last() { - let w = Paragraph::new(format!("{{{}}}", name)) - .alignment(Alignment::Right); + fn draw_branch_name(&self, f: &mut Frame) { + if let Some(name) = self.git_branch_name.last() { + let w = Paragraph::new(format!("{{{}}}", name)) + .alignment(Alignment::Right); - let rect = { - let mut rect = self.input.get_area(); - rect.height = 1; - rect.width = rect.width.saturating_sub(1); - rect - }; + let rect = { + let mut rect = self.input.get_area(); + rect.height = 1; + rect.width = rect.width.saturating_sub(1); + rect + }; - f.render_widget(w, rect); - } - } + f.render_widget(w, rect); + } + } - fn draw_warnings(&self, f: &mut Frame) { - let first_line = self - .input - .get_text() - .lines() - .next() - .map(str::len) - .unwrap_or_default(); + fn draw_warnings(&self, f: &mut Frame) { + let first_line = self + .input + .get_text() + .lines() + .next() + .map(str::len) + .unwrap_or_default(); - if first_line > FIRST_LINE_LIMIT { - let msg = strings::commit_first_line_warning(first_line); - let msg_length: u16 = msg.len().cast(); - let w = - Paragraph::new(msg).style(self.theme.text_danger()); + if first_line > FIRST_LINE_LIMIT { + let msg = strings::commit_first_line_warning(first_line); + let msg_length: u16 = msg.len().cast(); + let w = + Paragraph::new(msg).style(self.theme.text_danger()); - let rect = { - let mut rect = self.input.get_area(); - rect.y += rect.height.saturating_sub(1); - rect.height = 1; - let offset = - rect.width.saturating_sub(msg_length + 1); - rect.width = rect.width.saturating_sub(offset + 1); - rect.x += offset; + let rect = { + let mut rect = self.input.get_area(); + rect.y += rect.height.saturating_sub(1); + rect.height = 1; + let offset = + rect.width.saturating_sub(msg_length + 1); + rect.width = rect.width.saturating_sub(offset + 1); + rect.x += offset; - rect - }; + rect + }; - f.render_widget(w, rect); - } - } + f.render_widget(w, rect); + } + } - pub fn show_editor(&mut self) -> Result<()> { - let file_path = sync::repo_dir(CWD)?.join("COMMIT_EDITMSG"); + pub fn show_editor(&mut self) -> Result<()> { + let file_path = sync::repo_dir(CWD)?.join("COMMIT_EDITMSG"); - { - let mut file = File::create(&file_path)?; - file.write_fmt(format_args!( - "{}\n", - self.input.get_text() - ))?; - file.write_all( - strings::commit_editor_msg(&self.key_config) - .as_bytes(), - )?; - } + { + let mut file = File::create(&file_path)?; + file.write_fmt(format_args!( + "{}\n", + self.input.get_text() + ))?; + file.write_all( + strings::commit_editor_msg(&self.key_config) + .as_bytes(), + )?; + } - ExternalEditorComponent::open_file_in_editor(&file_path)?; + ExternalEditorComponent::open_file_in_editor(&file_path)?; - let mut message = String::new(); + let mut message = String::new(); - let mut file = File::open(&file_path)?; - file.read_to_string(&mut message)?; - drop(file); - std::fs::remove_file(&file_path)?; + let mut file = File::open(&file_path)?; + file.read_to_string(&mut message)?; + drop(file); + std::fs::remove_file(&file_path)?; - let message: String = message - .lines() - .flat_map(|l| { - if l.starts_with('#') { - vec![] - } else { - vec![l, "\n"] - } - }) - .collect(); + let message: String = message + .lines() + .flat_map(|l| { + if l.starts_with('#') { + vec![] + } else { + vec![l, "\n"] + } + }) + .collect(); - let message = message.trim().to_string(); + let message = message.trim().to_string(); - self.input.set_text(message); - self.input.show()?; + self.input.set_text(message); + self.input.show()?; - Ok(()) - } + Ok(()) + } - fn commit(&mut self) -> Result<()> { - let gpgsign = get_config_string(CWD, "commit.gpgsign") - .ok() - .flatten() - .and_then(|path| path.parse::().ok()) - .unwrap_or_default(); + fn commit(&mut self) -> Result<()> { + let gpgsign = get_config_string(CWD, "commit.gpgsign") + .ok() + .flatten() + .and_then(|path| path.parse::().ok()) + .unwrap_or_default(); - if gpgsign { - anyhow::bail!("config commit.gpgsign=true detected.\ngpg signing not supported.\ndeactivate in your repo/gitconfig to be able to commit without signing."); - } + if gpgsign { + anyhow::bail!("config commit.gpgsign=true detected.\ngpg signing not supported.\ndeactivate in your repo/gitconfig to be able to commit without signing."); + } - let msg = self.input.get_text().clone(); - self.input.clear(); - self.commit_with_msg(msg) - } + let msg = self.input.get_text().clone(); + self.input.clear(); + self.commit_with_msg(msg) + } - fn commit_with_msg(&mut self, msg: String) -> Result<()> { - if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? { - log::error!("pre-commit hook error: {}", e); - self.queue.push(InternalEvent::ShowErrorMsg(format!( - "pre-commit hook error:\n{}", - e - ))); - return Ok(()); - } - let mut msg = msg; - if let HookResult::NotOk(e) = - sync::hooks_commit_msg(CWD, &mut msg)? - { - log::error!("commit-msg hook error: {}", e); - self.queue.push(InternalEvent::ShowErrorMsg(format!( - "commit-msg hook error:\n{}", - e - ))); - return Ok(()); - } + fn commit_with_msg(&mut self, msg: String) -> Result<()> { + if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? { + log::error!("pre-commit hook error: {}", e); + self.queue.push(InternalEvent::ShowErrorMsg(format!( + "pre-commit hook error:\n{}", + e + ))); + return Ok(()); + } + let mut msg = msg; + if let HookResult::NotOk(e) = + sync::hooks_commit_msg(CWD, &mut msg)? + { + log::error!("commit-msg hook error: {}", e); + self.queue.push(InternalEvent::ShowErrorMsg(format!( + "commit-msg hook error:\n{}", + e + ))); + return Ok(()); + } - let res = match &self.mode { - Mode::Normal => sync::commit(CWD, &msg), - Mode::Amend(amend) => sync::amend(CWD, *amend, &msg), - Mode::Merge(ids) => sync::merge_commit(CWD, &msg, ids), - }; + let res = match &self.mode { + Mode::Normal => sync::commit(CWD, &msg), + Mode::Amend(amend) => sync::amend(CWD, *amend, &msg), + Mode::Merge(ids) => sync::merge_commit(CWD, &msg, ids), + }; - if let Err(e) = res { - log::error!("commit error: {}", &e); - self.queue.push(InternalEvent::ShowErrorMsg(format!( - "commit failed:\n{}", - &e - ))); - return Ok(()); - } + if let Err(e) = res { + log::error!("commit error: {}", &e); + self.queue.push(InternalEvent::ShowErrorMsg(format!( + "commit failed:\n{}", + &e + ))); + return Ok(()); + } - if let HookResult::NotOk(e) = sync::hooks_post_commit(CWD)? { - log::error!("post-commit hook error: {}", e); - self.queue.push(InternalEvent::ShowErrorMsg(format!( - "post-commit hook error:\n{}", - e - ))); - } + if let HookResult::NotOk(e) = sync::hooks_post_commit(CWD)? { + log::error!("post-commit hook error: {}", e); + self.queue.push(InternalEvent::ShowErrorMsg(format!( + "post-commit hook error:\n{}", + e + ))); + } - self.hide(); + self.hide(); - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - Ok(()) - } + Ok(()) + } - fn can_commit(&self) -> bool { - !self.is_empty() && self.is_changed() - } + fn can_commit(&self) -> bool { + !self.is_empty() && self.is_changed() + } - fn can_amend(&self) -> bool { - matches!(self.mode, Mode::Normal) - && sync::get_head(CWD).is_ok() - && (self.is_empty() || !self.is_changed()) - } + fn can_amend(&self) -> bool { + matches!(self.mode, Mode::Normal) + && sync::get_head(CWD).is_ok() + && (self.is_empty() || !self.is_changed()) + } - fn is_empty(&self) -> bool { - self.input.get_text().is_empty() - } + fn is_empty(&self) -> bool { + self.input.get_text().is_empty() + } - fn is_changed(&self) -> bool { - Some(self.input.get_text().trim()) - != self.commit_template.as_ref().map(|s| s.trim()) - } + fn is_changed(&self) -> bool { + Some(self.input.get_text().trim()) + != self.commit_template.as_ref().map(|s| s.trim()) + } - fn amend(&mut self) -> Result<()> { - if self.can_amend() { - let id = sync::get_head(CWD)?; - self.mode = Mode::Amend(id); + fn amend(&mut self) -> Result<()> { + if self.can_amend() { + let id = sync::get_head(CWD)?; + self.mode = Mode::Amend(id); - let details = sync::get_commit_details(CWD, id)?; + let details = sync::get_commit_details(CWD, id)?; - self.input.set_title(strings::commit_title_amend()); + self.input.set_title(strings::commit_title_amend()); - if let Some(msg) = details.message { - self.input.set_text(msg.combine()); - } - } + if let Some(msg) = details.message { + self.input.set_text(msg.combine()); + } + } - Ok(()) - } + Ok(()) + } } impl DrawableComponent for CommitComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.is_visible() { - self.input.draw(f, rect)?; - self.draw_branch_name(f); - self.draw_warnings(f); - } + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.is_visible() { + self.input.draw(f, rect)?; + self.draw_branch_name(f); + self.draw_warnings(f); + } - Ok(()) - } + Ok(()) + } } impl Component for CommitComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - self.input.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + self.input.commands(out, force_all); - if self.is_visible() || force_all { - out.push(CommandInfo::new( - strings::commands::commit_enter(&self.key_config), - self.can_commit(), - true, - )); + if self.is_visible() || force_all { + out.push(CommandInfo::new( + strings::commands::commit_enter(&self.key_config), + self.can_commit(), + true, + )); - out.push(CommandInfo::new( - strings::commands::commit_amend(&self.key_config), - self.can_amend(), - true, - )); + out.push(CommandInfo::new( + strings::commands::commit_amend(&self.key_config), + self.can_amend(), + true, + )); - out.push(CommandInfo::new( - strings::commands::commit_open_editor( - &self.key_config, - ), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::commit_open_editor( + &self.key_config, + ), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { - if self.input.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if self.input.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if let Event::Key(e) = ev { - if e == self.key_config.enter && self.can_commit() { - try_or_popup!( - self, - "commit error:", - self.commit() - ); - } else if e == self.key_config.commit_amend - && self.can_amend() - { - self.amend()?; - } else if e == self.key_config.open_commit_editor { - self.queue.push( - InternalEvent::OpenExternalEditor(None), - ); - self.hide(); - } else { - } - // stop key event propagation - return Ok(EventState::Consumed); - } - } + if let Event::Key(e) = ev { + if e == self.key_config.enter && self.can_commit() { + try_or_popup!( + self, + "commit error:", + self.commit() + ); + } else if e == self.key_config.commit_amend + && self.can_amend() + { + self.amend()?; + } else if e == self.key_config.open_commit_editor { + self.queue.push( + InternalEvent::OpenExternalEditor(None), + ); + self.hide(); + } else { + } + // stop key event propagation + return Ok(EventState::Consumed); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.input.is_visible() - } + fn is_visible(&self) -> bool { + self.input.is_visible() + } - fn hide(&mut self) { - self.input.hide(); - } + fn hide(&mut self) { + self.input.hide(); + } - fn show(&mut self) -> Result<()> { - //only clear text if it was not a normal commit dlg before, so to preserve old commit msg that was edited - if !matches!(self.mode, Mode::Normal) { - self.input.clear(); - } + fn show(&mut self) -> Result<()> { + //only clear text if it was not a normal commit dlg before, so to preserve old commit msg that was edited + if !matches!(self.mode, Mode::Normal) { + self.input.clear(); + } - self.mode = Mode::Normal; + self.mode = Mode::Normal; - self.mode = if sync::repo_state(CWD)? == RepoState::Merge { - let ids = sync::mergehead_ids(CWD)?; - self.input.set_title(strings::commit_title_merge()); - self.input.set_text(sync::merge_msg(CWD)?); - Mode::Merge(ids) - } else { - self.commit_template = - get_config_string(CWD, "commit.template") - .ok() - .flatten() - .and_then(|path| read_to_string(path).ok()); + self.mode = if sync::repo_state(CWD)? == RepoState::Merge { + let ids = sync::mergehead_ids(CWD)?; + self.input.set_title(strings::commit_title_merge()); + self.input.set_text(sync::merge_msg(CWD)?); + Mode::Merge(ids) + } else { + self.commit_template = + get_config_string(CWD, "commit.template") + .ok() + .flatten() + .and_then(|path| read_to_string(path).ok()); - if self.is_empty() { - if let Some(s) = &self.commit_template { - self.input.set_text(s.clone()); - } - } + if self.is_empty() { + if let Some(s) = &self.commit_template { + self.input.set_text(s.clone()); + } + } - self.input.set_title(strings::commit_title()); - Mode::Normal - }; + self.input.set_title(strings::commit_title()); + Mode::Normal + }; - self.input.show()?; + self.input.show()?; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/commit_details/details.rs b/src/components/commit_details/details.rs index 138cc0b6..2e8b69b3 100644 --- a/src/components/commit_details/details.rs +++ b/src/components/commit_details/details.rs @@ -1,18 +1,18 @@ use crate::{ - components::{ - dialog_paragraph, - utils::{scroll_vertical::VerticalScroll, time_to_string}, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, ScrollType, - }, - keys::SharedKeyConfig, - strings::{self, order}, - ui::style::SharedTheme, + components::{ + dialog_paragraph, + utils::{scroll_vertical::VerticalScroll, time_to_string}, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, ScrollType, + }, + keys::SharedKeyConfig, + strings::{self, order}, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{self, CommitDetails, CommitId, CommitMessage}, - CWD, + sync::{self, CommitDetails, CommitId, CommitMessage}, + CWD, }; use crossterm::event::Event; use itertools::Itertools; @@ -20,493 +20,493 @@ use std::clone::Clone; use std::{borrow::Cow, cell::Cell}; use sync::CommitTags; use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - style::{Modifier, Style}, - text::{Span, Spans, Text}, - Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + style::{Modifier, Style}, + text::{Span, Spans, Text}, + Frame, }; enum Detail { - Author, - Date, - Commiter, - Sha, + Author, + Date, + Commiter, + Sha, } pub struct DetailsComponent { - data: Option, - tags: Vec, - theme: SharedTheme, - focused: bool, - current_width: Cell, - scroll: VerticalScroll, - scroll_to_bottom_next_draw: Cell, - key_config: SharedKeyConfig, + data: Option, + tags: Vec, + theme: SharedTheme, + focused: bool, + current_width: Cell, + scroll: VerticalScroll, + scroll_to_bottom_next_draw: Cell, + key_config: SharedKeyConfig, } type WrappedCommitMessage<'a> = - (Vec>, Vec>); + (Vec>, Vec>); impl DetailsComponent { - /// - pub const fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - focused: bool, - ) -> Self { - Self { - data: None, - tags: Vec::new(), - theme, - focused, - scroll_to_bottom_next_draw: Cell::new(false), - current_width: Cell::new(0), - scroll: VerticalScroll::new(), - key_config, - } - } + /// + pub const fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + focused: bool, + ) -> Self { + Self { + data: None, + tags: Vec::new(), + theme, + focused, + scroll_to_bottom_next_draw: Cell::new(false), + current_width: Cell::new(0), + scroll: VerticalScroll::new(), + key_config, + } + } - pub fn set_commit( - &mut self, - id: Option, - tags: Option, - ) { - self.tags.clear(); + pub fn set_commit( + &mut self, + id: Option, + tags: Option, + ) { + self.tags.clear(); - self.data = - id.and_then(|id| sync::get_commit_details(CWD, id).ok()); + self.data = + id.and_then(|id| sync::get_commit_details(CWD, id).ok()); - self.scroll.reset(); + self.scroll.reset(); - if let Some(tags) = tags { - self.tags.extend(tags); - } - } + if let Some(tags) = tags { + self.tags.extend(tags); + } + } - fn wrap_commit_details( - message: &CommitMessage, - width: usize, - ) -> WrappedCommitMessage<'_> { - let wrapped_title = textwrap::wrap(&message.subject, width); + fn wrap_commit_details( + message: &CommitMessage, + width: usize, + ) -> WrappedCommitMessage<'_> { + let wrapped_title = textwrap::wrap(&message.subject, width); - if let Some(ref body) = message.body { - let wrapped_message: Vec> = - textwrap::wrap(body, width).into_iter().collect(); + if let Some(ref body) = message.body { + let wrapped_message: Vec> = + textwrap::wrap(body, width).into_iter().collect(); - (wrapped_title, wrapped_message) - } else { - (wrapped_title, vec![]) - } - } + (wrapped_title, wrapped_message) + } else { + (wrapped_title, vec![]) + } + } - fn get_wrapped_lines( - data: &Option, - width: usize, - ) -> WrappedCommitMessage<'_> { - if let Some(ref data) = data { - if let Some(ref message) = data.message { - return Self::wrap_commit_details(message, width); - } - } + fn get_wrapped_lines( + data: &Option, + width: usize, + ) -> WrappedCommitMessage<'_> { + if let Some(ref data) = data { + if let Some(ref message) = data.message { + return Self::wrap_commit_details(message, width); + } + } - (vec![], vec![]) - } + (vec![], vec![]) + } - fn get_number_of_lines( - details: &Option, - width: usize, - ) -> usize { - let (wrapped_title, wrapped_message) = - Self::get_wrapped_lines(details, width); + fn get_number_of_lines( + details: &Option, + width: usize, + ) -> usize { + let (wrapped_title, wrapped_message) = + Self::get_wrapped_lines(details, width); - wrapped_title.len() + wrapped_message.len() - } + wrapped_title.len() + wrapped_message.len() + } - fn get_theme_for_line(&self, bold: bool) -> Style { - if bold { - self.theme.text(true, false).add_modifier(Modifier::BOLD) - } else { - self.theme.text(true, false) - } - } + fn get_theme_for_line(&self, bold: bool) -> Style { + if bold { + self.theme.text(true, false).add_modifier(Modifier::BOLD) + } else { + self.theme.text(true, false) + } + } - fn get_wrapped_text_message( - &self, - width: usize, - height: usize, - ) -> Vec { - let (wrapped_title, wrapped_message) = - Self::get_wrapped_lines(&self.data, width); + fn get_wrapped_text_message( + &self, + width: usize, + height: usize, + ) -> Vec { + let (wrapped_title, wrapped_message) = + Self::get_wrapped_lines(&self.data, width); - [&wrapped_title[..], &wrapped_message[..]] - .concat() - .iter() - .enumerate() - .skip(self.scroll.get_top()) - .take(height) - .map(|(i, line)| { - Spans::from(vec![Span::styled( - line.clone(), - self.get_theme_for_line(i < wrapped_title.len()), - )]) - }) - .collect() - } + [&wrapped_title[..], &wrapped_message[..]] + .concat() + .iter() + .enumerate() + .skip(self.scroll.get_top()) + .take(height) + .map(|(i, line)| { + Spans::from(vec![Span::styled( + line.clone(), + self.get_theme_for_line(i < wrapped_title.len()), + )]) + }) + .collect() + } - fn style_detail(&self, field: &Detail) -> Span { - match field { - Detail::Author => Span::styled( - Cow::from(strings::commit::details_author( - &self.key_config, - )), - self.theme.text(false, false), - ), - Detail::Date => Span::styled( - Cow::from(strings::commit::details_date( - &self.key_config, - )), - self.theme.text(false, false), - ), - Detail::Commiter => Span::styled( - Cow::from(strings::commit::details_committer( - &self.key_config, - )), - self.theme.text(false, false), - ), - Detail::Sha => Span::styled( - Cow::from(strings::commit::details_tags( - &self.key_config, - )), - self.theme.text(false, false), - ), - } - } + fn style_detail(&self, field: &Detail) -> Span { + match field { + Detail::Author => Span::styled( + Cow::from(strings::commit::details_author( + &self.key_config, + )), + self.theme.text(false, false), + ), + Detail::Date => Span::styled( + Cow::from(strings::commit::details_date( + &self.key_config, + )), + self.theme.text(false, false), + ), + Detail::Commiter => Span::styled( + Cow::from(strings::commit::details_committer( + &self.key_config, + )), + self.theme.text(false, false), + ), + Detail::Sha => Span::styled( + Cow::from(strings::commit::details_tags( + &self.key_config, + )), + self.theme.text(false, false), + ), + } + } - #[allow(unstable_name_collisions)] - fn get_text_info(&self) -> Vec { - if let Some(ref data) = self.data { - let mut res = vec![ - Spans::from(vec![ - self.style_detail(&Detail::Author), - Span::styled( - Cow::from(format!( - "{} <{}>", - data.author.name, data.author.email - )), - self.theme.text(true, false), - ), - ]), - Spans::from(vec![ - self.style_detail(&Detail::Date), - Span::styled( - Cow::from(time_to_string( - data.author.time, - false, - )), - self.theme.text(true, false), - ), - ]), - ]; + #[allow(unstable_name_collisions)] + fn get_text_info(&self) -> Vec { + if let Some(ref data) = self.data { + let mut res = vec![ + Spans::from(vec![ + self.style_detail(&Detail::Author), + Span::styled( + Cow::from(format!( + "{} <{}>", + data.author.name, data.author.email + )), + self.theme.text(true, false), + ), + ]), + Spans::from(vec![ + self.style_detail(&Detail::Date), + Span::styled( + Cow::from(time_to_string( + data.author.time, + false, + )), + self.theme.text(true, false), + ), + ]), + ]; - if let Some(ref committer) = data.committer { - res.extend(vec![ - Spans::from(vec![ - self.style_detail(&Detail::Commiter), - Span::styled( - Cow::from(format!( - "{} <{}>", - committer.name, committer.email - )), - self.theme.text(true, false), - ), - ]), - Spans::from(vec![ - self.style_detail(&Detail::Date), - Span::styled( - Cow::from(time_to_string( - committer.time, - false, - )), - self.theme.text(true, false), - ), - ]), - ]); - } + if let Some(ref committer) = data.committer { + res.extend(vec![ + Spans::from(vec![ + self.style_detail(&Detail::Commiter), + Span::styled( + Cow::from(format!( + "{} <{}>", + committer.name, committer.email + )), + self.theme.text(true, false), + ), + ]), + Spans::from(vec![ + self.style_detail(&Detail::Date), + Span::styled( + Cow::from(time_to_string( + committer.time, + false, + )), + self.theme.text(true, false), + ), + ]), + ]); + } - res.push(Spans::from(vec![ - Span::styled( - Cow::from(strings::commit::details_sha( - &self.key_config, - )), - self.theme.text(false, false), - ), - Span::styled( - Cow::from(data.hash.clone()), - self.theme.text(true, false), - ), - ])); + res.push(Spans::from(vec![ + Span::styled( + Cow::from(strings::commit::details_sha( + &self.key_config, + )), + self.theme.text(false, false), + ), + Span::styled( + Cow::from(data.hash.clone()), + self.theme.text(true, false), + ), + ])); - if !self.tags.is_empty() { - res.push(Spans::from( - self.style_detail(&Detail::Sha), - )); + if !self.tags.is_empty() { + res.push(Spans::from( + self.style_detail(&Detail::Sha), + )); - res.push(Spans::from( - self.tags - .iter() - .map(|tag| { - Span::styled( - Cow::from(tag), - self.theme.text(true, false), - ) - }) - .intersperse(Span::styled( - Cow::from(","), - self.theme.text(true, false), - )) - .collect::>(), - )); - } + res.push(Spans::from( + self.tags + .iter() + .map(|tag| { + Span::styled( + Cow::from(tag), + self.theme.text(true, false), + ) + }) + .intersperse(Span::styled( + Cow::from(","), + self.theme.text(true, false), + )) + .collect::>(), + )); + } - res - } else { - vec![] - } - } + res + } else { + vec![] + } + } - fn move_scroll_top(&mut self, move_type: ScrollType) -> bool { - if self.data.is_some() { - self.scroll.move_top(move_type) - } else { - false - } - } + fn move_scroll_top(&mut self, move_type: ScrollType) -> bool { + if self.data.is_some() { + self.scroll.move_top(move_type) + } else { + false + } + } } impl DrawableComponent for DetailsComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - const CANSCROLL_STRING: &str = "[\u{2026}]"; - const EMPTY_STRING: &str = ""; + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + const CANSCROLL_STRING: &str = "[\u{2026}]"; + const EMPTY_STRING: &str = ""; - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [Constraint::Length(8), Constraint::Min(10)].as_ref(), - ) - .split(rect); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [Constraint::Length(8), Constraint::Min(10)].as_ref(), + ) + .split(rect); - f.render_widget( - dialog_paragraph( - &strings::commit::details_info_title( - &self.key_config, - ), - Text::from(self.get_text_info()), - &self.theme, - false, - ), - chunks[0], - ); + f.render_widget( + dialog_paragraph( + &strings::commit::details_info_title( + &self.key_config, + ), + Text::from(self.get_text_info()), + &self.theme, + false, + ), + chunks[0], + ); - // We have to take the border into account which is one character on - // each side. - let border_width: u16 = 2; + // We have to take the border into account which is one character on + // each side. + let border_width: u16 = 2; - let width = chunks[1].width.saturating_sub(border_width); - let height = chunks[1].height.saturating_sub(border_width); + let width = chunks[1].width.saturating_sub(border_width); + let height = chunks[1].height.saturating_sub(border_width); - self.current_width.set(width); + self.current_width.set(width); - let number_of_lines = - Self::get_number_of_lines(&self.data, usize::from(width)); + let number_of_lines = + Self::get_number_of_lines(&self.data, usize::from(width)); - self.scroll.update_no_selection( - number_of_lines, - usize::from(height), - ); + self.scroll.update_no_selection( + number_of_lines, + usize::from(height), + ); - if self.scroll_to_bottom_next_draw.get() { - self.scroll.move_top(ScrollType::End); - self.scroll_to_bottom_next_draw.set(false); - } + if self.scroll_to_bottom_next_draw.get() { + self.scroll.move_top(ScrollType::End); + self.scroll_to_bottom_next_draw.set(false); + } - let can_scroll = usize::from(height) < number_of_lines; + let can_scroll = usize::from(height) < number_of_lines; - f.render_widget( - dialog_paragraph( - &format!( - "{} {}", - strings::commit::details_message_title( - &self.key_config, - ), - if !self.focused && can_scroll { - CANSCROLL_STRING - } else { - EMPTY_STRING - } - ), - Text::from(self.get_wrapped_text_message( - width as usize, - height as usize, - )), - &self.theme, - self.focused, - ), - chunks[1], - ); + f.render_widget( + dialog_paragraph( + &format!( + "{} {}", + strings::commit::details_message_title( + &self.key_config, + ), + if !self.focused && can_scroll { + CANSCROLL_STRING + } else { + EMPTY_STRING + } + ), + Text::from(self.get_wrapped_text_message( + width as usize, + height as usize, + )), + &self.theme, + self.focused, + ), + chunks[1], + ); - if self.focused { - self.scroll.draw(f, chunks[1], &self.theme); - } + if self.focused { + self.scroll.draw(f, chunks[1], &self.theme); + } - Ok(()) - } + Ok(()) + } } impl Component for DetailsComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - let width = usize::from(self.current_width.get()); - let number_of_lines = - Self::get_number_of_lines(&self.data, width); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + let width = usize::from(self.current_width.get()); + let number_of_lines = + Self::get_number_of_lines(&self.data, width); - out.push( - CommandInfo::new( - strings::commands::navigate_commit_message( - &self.key_config, - ), - number_of_lines > 0, - self.focused || force_all, - ) - .order(order::NAV), - ); + out.push( + CommandInfo::new( + strings::commands::navigate_commit_message( + &self.key_config, + ), + number_of_lines > 0, + self.focused || force_all, + ) + .order(order::NAV), + ); - CommandBlocking::PassingOn - } + CommandBlocking::PassingOn + } - fn event(&mut self, event: Event) -> Result { - if self.focused { - if let Event::Key(e) = event { - return Ok(if e == self.key_config.move_up { - self.move_scroll_top(ScrollType::Up).into() - } else if e == self.key_config.move_down { - self.move_scroll_top(ScrollType::Down).into() - } else if e == self.key_config.home - || e == self.key_config.shift_up - { - self.move_scroll_top(ScrollType::Home).into() - } else if e == self.key_config.end - || e == self.key_config.shift_down - { - self.move_scroll_top(ScrollType::End).into() - } else { - EventState::NotConsumed - }); - } - } + fn event(&mut self, event: Event) -> Result { + if self.focused { + if let Event::Key(e) = event { + return Ok(if e == self.key_config.move_up { + self.move_scroll_top(ScrollType::Up).into() + } else if e == self.key_config.move_down { + self.move_scroll_top(ScrollType::Down).into() + } else if e == self.key_config.home + || e == self.key_config.shift_up + { + self.move_scroll_top(ScrollType::Home).into() + } else if e == self.key_config.end + || e == self.key_config.shift_down + { + self.move_scroll_top(ScrollType::End).into() + } else { + EventState::NotConsumed + }); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn focused(&self) -> bool { - self.focused - } + fn focused(&self) -> bool { + self.focused + } - fn focus(&mut self, focus: bool) { - if focus { - self.scroll_to_bottom_next_draw.set(true); - } else { - self.scroll.reset(); - } + fn focus(&mut self, focus: bool) { + if focus { + self.scroll_to_bottom_next_draw.set(true); + } else { + self.scroll.reset(); + } - self.focused = focus; - } + self.focused = focus; + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - fn get_wrapped_lines( - message: &CommitMessage, - width: usize, - ) -> Vec> { - let (wrapped_title, wrapped_message) = - DetailsComponent::wrap_commit_details(&message, width); + fn get_wrapped_lines( + message: &CommitMessage, + width: usize, + ) -> Vec> { + let (wrapped_title, wrapped_message) = + DetailsComponent::wrap_commit_details(&message, width); - [&wrapped_title[..], &wrapped_message[..]].concat() - } + [&wrapped_title[..], &wrapped_message[..]].concat() + } - #[test] - fn test_textwrap() { - let message = CommitMessage::from("Commit message"); + #[test] + fn test_textwrap() { + let message = CommitMessage::from("Commit message"); - assert_eq!( - get_wrapped_lines(&message, 7), - vec!["Commit", "message"] - ); - assert_eq!( - get_wrapped_lines(&message, 14), - vec!["Commit message"] - ); + assert_eq!( + get_wrapped_lines(&message, 7), + vec!["Commit", "message"] + ); + assert_eq!( + get_wrapped_lines(&message, 14), + vec!["Commit message"] + ); - let message_with_newline = - CommitMessage::from("Commit message\n"); + let message_with_newline = + CommitMessage::from("Commit message\n"); - assert_eq!( - get_wrapped_lines(&message_with_newline, 7), - vec!["Commit", "message"] - ); - assert_eq!( - get_wrapped_lines(&message_with_newline, 14), - vec!["Commit message"] - ); + assert_eq!( + get_wrapped_lines(&message_with_newline, 7), + vec!["Commit", "message"] + ); + assert_eq!( + get_wrapped_lines(&message_with_newline, 14), + vec!["Commit message"] + ); - let message_with_body = CommitMessage::from( - "Commit message\nFirst line\nSecond line", - ); + let message_with_body = CommitMessage::from( + "Commit message\nFirst line\nSecond line", + ); - assert_eq!( - get_wrapped_lines(&message_with_body, 7), - vec![ - "Commit", "message", "First", "line", "Second", - "line" - ] - ); - assert_eq!( - get_wrapped_lines(&message_with_body, 14), - vec!["Commit message", "First line", "Second line"] - ); - } + assert_eq!( + get_wrapped_lines(&message_with_body, 7), + vec![ + "Commit", "message", "First", "line", "Second", + "line" + ] + ); + assert_eq!( + get_wrapped_lines(&message_with_body, 14), + vec!["Commit message", "First line", "Second line"] + ); + } } #[cfg(test)] mod test_line_count { - use super::*; + use super::*; - #[test] - fn test_smoke() { - let commit = CommitDetails { - message: Some(CommitMessage { - subject: String::from("subject line"), - body: Some(String::from("body lone")), - }), - ..CommitDetails::default() - }; - let lines = DetailsComponent::get_number_of_lines( - &Some(commit.clone()), - 50, - ); - assert_eq!(lines, 2); + #[test] + fn test_smoke() { + let commit = CommitDetails { + message: Some(CommitMessage { + subject: String::from("subject line"), + body: Some(String::from("body lone")), + }), + ..CommitDetails::default() + }; + let lines = DetailsComponent::get_number_of_lines( + &Some(commit.clone()), + 50, + ); + assert_eq!(lines, 2); - let lines = - DetailsComponent::get_number_of_lines(&Some(commit), 8); - assert_eq!(lines, 4); - } + let lines = + DetailsComponent::get_number_of_lines(&Some(commit), 8); + assert_eq!(lines, 4); + } } diff --git a/src/components/commit_details/mod.rs b/src/components/commit_details/mod.rs index 7966a388..3afd9926 100644 --- a/src/components/commit_details/mod.rs +++ b/src/components/commit_details/mod.rs @@ -1,210 +1,210 @@ mod details; use super::{ - command_pump, event_pump, CommandBlocking, CommandInfo, - Component, DrawableComponent, EventState, FileTreeComponent, + command_pump, event_pump, CommandBlocking, CommandInfo, + Component, DrawableComponent, EventState, FileTreeComponent, }; use crate::{ - accessors, keys::SharedKeyConfig, queue::Queue, strings, - ui::style::SharedTheme, + accessors, keys::SharedKeyConfig, queue::Queue, strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{CommitId, CommitTags}, - AsyncCommitFiles, AsyncGitNotification, + sync::{CommitId, CommitTags}, + AsyncCommitFiles, AsyncGitNotification, }; use crossbeam_channel::Sender; use crossterm::event::Event; use details::DetailsComponent; use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + Frame, }; pub struct CommitDetailsComponent { - details: DetailsComponent, - file_tree: FileTreeComponent, - git_commit_files: AsyncCommitFiles, - visible: bool, - key_config: SharedKeyConfig, + details: DetailsComponent, + file_tree: FileTreeComponent, + git_commit_files: AsyncCommitFiles, + visible: bool, + key_config: SharedKeyConfig, } impl CommitDetailsComponent { - accessors!(self, [details, file_tree]); + accessors!(self, [details, file_tree]); - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - details: DetailsComponent::new( - theme.clone(), - key_config.clone(), - false, - ), - git_commit_files: AsyncCommitFiles::new(sender), - file_tree: FileTreeComponent::new( - "", - false, - Some(queue.clone()), - theme, - key_config.clone(), - ), - visible: false, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + details: DetailsComponent::new( + theme.clone(), + key_config.clone(), + false, + ), + git_commit_files: AsyncCommitFiles::new(sender), + file_tree: FileTreeComponent::new( + "", + false, + Some(queue.clone()), + theme, + key_config.clone(), + ), + visible: false, + key_config, + } + } - fn get_files_title(&self) -> String { - let files_count = self.file_tree.file_count(); + fn get_files_title(&self) -> String { + let files_count = self.file_tree.file_count(); - format!( - "{} {}", - strings::commit::details_files_title(&self.key_config), - files_count - ) - } + format!( + "{} {}", + strings::commit::details_files_title(&self.key_config), + files_count + ) + } - /// - pub fn set_commit( - &mut self, - id: Option, - tags: Option, - ) -> Result<()> { - self.details.set_commit(id, tags); + /// + pub fn set_commit( + &mut self, + id: Option, + tags: Option, + ) -> Result<()> { + self.details.set_commit(id, tags); - if let Some(id) = id { - if let Some((fetched_id, res)) = - self.git_commit_files.current()? - { - if fetched_id == id { - self.file_tree.update(res.as_slice())?; - self.file_tree.set_title(self.get_files_title()); + if let Some(id) = id { + if let Some((fetched_id, res)) = + self.git_commit_files.current()? + { + if fetched_id == id { + self.file_tree.update(res.as_slice())?; + self.file_tree.set_title(self.get_files_title()); - return Ok(()); - } - } + return Ok(()); + } + } - self.file_tree.clear()?; - self.git_commit_files.fetch(id)?; - } + self.file_tree.clear()?; + self.git_commit_files.fetch(id)?; + } - self.file_tree.set_title(self.get_files_title()); + self.file_tree.set_title(self.get_files_title()); - Ok(()) - } + Ok(()) + } - /// - pub fn any_work_pending(&self) -> bool { - self.git_commit_files.is_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.git_commit_files.is_pending() + } - /// - pub const fn files(&self) -> &FileTreeComponent { - &self.file_tree - } + /// + pub const fn files(&self) -> &FileTreeComponent { + &self.file_tree + } } impl DrawableComponent for CommitDetailsComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - let percentages = if self.file_tree.focused() { - (40, 60) - } else if self.details.focused() { - (60, 40) - } else { - (40, 60) - }; + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + let percentages = if self.file_tree.focused() { + (40, 60) + } else if self.details.focused() { + (60, 40) + } else { + (40, 60) + }; - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(percentages.0), - Constraint::Percentage(percentages.1), - ] - .as_ref(), - ) - .split(rect); + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(percentages.0), + Constraint::Percentage(percentages.1), + ] + .as_ref(), + ) + .split(rect); - self.details.draw(f, chunks[0])?; - self.file_tree.draw(f, chunks[1])?; + self.details.draw(f, chunks[0])?; + self.file_tree.draw(f, chunks[1])?; - Ok(()) - } + Ok(()) + } } impl Component for CommitDetailsComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - command_pump( - out, - force_all, - self.components().as_slice(), - ); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + command_pump( + out, + force_all, + self.components().as_slice(), + ); + } - CommandBlocking::PassingOn - } + CommandBlocking::PassingOn + } - fn event(&mut self, ev: Event) -> Result { - if event_pump(ev, self.components_mut().as_mut_slice())? - .is_consumed() - { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if event_pump(ev, self.components_mut().as_mut_slice())? + .is_consumed() + { + return Ok(EventState::Consumed); + } - if self.focused() { - if let Event::Key(e) = ev { - return if e == self.key_config.focus_below - && self.details.focused() - { - self.details.focus(false); - self.file_tree.focus(true); - Ok(EventState::Consumed) - } else if e == self.key_config.focus_above - && self.file_tree.focused() - { - self.file_tree.focus(false); - self.details.focus(true); - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - }; - } - } + if self.focused() { + if let Event::Key(e) = ev { + return if e == self.key_config.focus_below + && self.details.focused() + { + self.details.focus(false); + self.file_tree.focus(true); + Ok(EventState::Consumed) + } else if e == self.key_config.focus_above + && self.file_tree.focused() + { + self.file_tree.focus(false); + self.details.focus(true); + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + }; + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } - fn hide(&mut self) { - self.visible = false; - } - fn show(&mut self) -> Result<()> { - self.visible = true; - Ok(()) - } + fn is_visible(&self) -> bool { + self.visible + } + fn hide(&mut self) { + self.visible = false; + } + fn show(&mut self) -> Result<()> { + self.visible = true; + Ok(()) + } - fn focused(&self) -> bool { - self.details.focused() || self.file_tree.focused() - } - fn focus(&mut self, focus: bool) { - self.details.focus(false); - self.file_tree.focus(focus); - self.file_tree.show_selection(true); - } + fn focused(&self) -> bool { + self.details.focused() || self.file_tree.focused() + } + fn focus(&mut self, focus: bool) { + self.details.focus(false); + self.file_tree.focus(focus); + self.file_tree.show_selection(true); + } } diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 332a611a..04a57f2e 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -1,466 +1,465 @@ use super::utils::logitems::{ItemBatch, LogEntry}; use crate::{ - components::{ - utils::string_width_align, CommandBlocking, CommandInfo, - Component, DrawableComponent, EventState, ScrollType, - }, - keys::SharedKeyConfig, - strings, - ui::calc_scroll_top, - ui::style::{SharedTheme, Theme}, + components::{ + utils::string_width_align, CommandBlocking, CommandInfo, + Component, DrawableComponent, EventState, ScrollType, + }, + keys::SharedKeyConfig, + strings, + ui::calc_scroll_top, + ui::style::{SharedTheme, Theme}, }; use anyhow::Result; use asyncgit::sync::{CommitId, Tags}; use chrono::{DateTime, Local}; use crossterm::event::Event; use std::{ - borrow::Cow, cell::Cell, cmp, convert::TryFrom, time::Instant, + borrow::Cow, cell::Cell, cmp, convert::TryFrom, time::Instant, }; use tui::{ - backend::Backend, - layout::{Alignment, Rect}, - text::{Span, Spans}, - widgets::{Block, Borders, Paragraph}, - Frame, + backend::Backend, + layout::{Alignment, Rect}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, + Frame, }; const ELEMENTS_PER_LINE: usize = 10; /// pub struct CommitList { - title: String, - selection: usize, - branch: Option, - count_total: usize, - items: ItemBatch, - marked: Vec, - scroll_state: (Instant, f32), - tags: Option, - current_size: Cell<(u16, u16)>, - scroll_top: Cell, - theme: SharedTheme, - key_config: SharedKeyConfig, + title: String, + selection: usize, + branch: Option, + count_total: usize, + items: ItemBatch, + marked: Vec, + scroll_state: (Instant, f32), + tags: Option, + current_size: Cell<(u16, u16)>, + scroll_top: Cell, + theme: SharedTheme, + key_config: SharedKeyConfig, } impl CommitList { - /// - pub fn new( - title: &str, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - items: ItemBatch::default(), - marked: Vec::with_capacity(2), - selection: 0, - branch: None, - count_total: 0, - scroll_state: (Instant::now(), 0_f32), - tags: None, - current_size: Cell::new((0, 0)), - scroll_top: Cell::new(0), - theme, - key_config, - title: String::from(title), - } - } + /// + pub fn new( + title: &str, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + items: ItemBatch::default(), + marked: Vec::with_capacity(2), + selection: 0, + branch: None, + count_total: 0, + scroll_state: (Instant::now(), 0_f32), + tags: None, + current_size: Cell::new((0, 0)), + scroll_top: Cell::new(0), + theme, + key_config, + title: String::from(title), + } + } - /// - pub fn items(&mut self) -> &mut ItemBatch { - &mut self.items - } + /// + pub fn items(&mut self) -> &mut ItemBatch { + &mut self.items + } - /// - pub fn set_branch(&mut self, name: Option) { - self.branch = name; - } + /// + pub fn set_branch(&mut self, name: Option) { + self.branch = name; + } - /// - pub const fn selection(&self) -> usize { - self.selection - } + /// + pub const fn selection(&self) -> usize { + self.selection + } - /// - pub fn current_size(&self) -> (u16, u16) { - self.current_size.get() - } + /// + pub fn current_size(&self) -> (u16, u16) { + self.current_size.get() + } - /// - pub fn set_count_total(&mut self, total: usize) { - self.count_total = total; - self.selection = - cmp::min(self.selection, self.selection_max()); - } + /// + pub fn set_count_total(&mut self, total: usize) { + self.count_total = total; + self.selection = + cmp::min(self.selection, self.selection_max()); + } - /// - #[allow(clippy::missing_const_for_fn)] - pub fn selection_max(&self) -> usize { - self.count_total.saturating_sub(1) - } + /// + #[allow(clippy::missing_const_for_fn)] + pub fn selection_max(&self) -> usize { + self.count_total.saturating_sub(1) + } - /// - pub const fn tags(&self) -> Option<&Tags> { - self.tags.as_ref() - } + /// + pub const fn tags(&self) -> Option<&Tags> { + self.tags.as_ref() + } - /// - pub fn clear(&mut self) { - self.items.clear(); - } + /// + pub fn clear(&mut self) { + self.items.clear(); + } - /// - pub fn set_tags(&mut self, tags: Tags) { - self.tags = Some(tags); - } + /// + pub fn set_tags(&mut self, tags: Tags) { + self.tags = Some(tags); + } - /// - pub fn selected_entry(&self) -> Option<&LogEntry> { - self.items.iter().nth( - self.selection.saturating_sub(self.items.index_offset()), - ) - } + /// + pub fn selected_entry(&self) -> Option<&LogEntry> { + self.items.iter().nth( + self.selection.saturating_sub(self.items.index_offset()), + ) + } - pub fn copy_entry_hash(&self) -> Result<()> { - if let Some(e) = self.items.iter().nth( - self.selection.saturating_sub(self.items.index_offset()), - ) { - crate::clipboard::copy_string(&e.hash_short)?; - } - Ok(()) - } + pub fn copy_entry_hash(&self) -> Result<()> { + if let Some(e) = self.items.iter().nth( + self.selection.saturating_sub(self.items.index_offset()), + ) { + crate::clipboard::copy_string(&e.hash_short)?; + } + Ok(()) + } - fn move_selection(&mut self, scroll: ScrollType) -> Result { - self.update_scroll_speed(); + fn move_selection(&mut self, scroll: ScrollType) -> Result { + self.update_scroll_speed(); - #[allow(clippy::cast_possible_truncation)] - let speed_int = - usize::try_from(self.scroll_state.1 as i64)?.max(1); + #[allow(clippy::cast_possible_truncation)] + let speed_int = usize::try_from(self.scroll_state.1 as i64)?.max(1); - let page_offset = - usize::from(self.current_size.get().1).saturating_sub(1); + let page_offset = + usize::from(self.current_size.get().1).saturating_sub(1); - let new_selection = match scroll { - ScrollType::Up => { - self.selection.saturating_sub(speed_int) - } - ScrollType::Down => { - self.selection.saturating_add(speed_int) - } - ScrollType::PageUp => { - self.selection.saturating_sub(page_offset) - } - ScrollType::PageDown => { - self.selection.saturating_add(page_offset) - } - ScrollType::Home => 0, - ScrollType::End => self.selection_max(), - }; + let new_selection = match scroll { + ScrollType::Up => { + self.selection.saturating_sub(speed_int) + } + ScrollType::Down => { + self.selection.saturating_add(speed_int) + } + ScrollType::PageUp => { + self.selection.saturating_sub(page_offset) + } + ScrollType::PageDown => { + self.selection.saturating_add(page_offset) + } + ScrollType::Home => 0, + ScrollType::End => self.selection_max(), + }; - let new_selection = - cmp::min(new_selection, self.selection_max()); + let new_selection = + cmp::min(new_selection, self.selection_max()); - let needs_update = new_selection != self.selection; + let needs_update = new_selection != self.selection; - self.selection = new_selection; + self.selection = new_selection; - Ok(needs_update) - } + Ok(needs_update) + } - fn mark(&mut self) { - if let Some(e) = self.selected_entry() { - let id = e.id; - if self.is_marked(&id).unwrap_or_default() { - self.marked.retain(|marked| marked != &id); - } else { - self.marked.push(id); - } - } - } + fn mark(&mut self) { + if let Some(e) = self.selected_entry() { + let id = e.id; + if self.is_marked(&id).unwrap_or_default() { + self.marked.retain(|marked| marked != &id); + } else { + self.marked.push(id); + } + } + } - fn update_scroll_speed(&mut self) { - const REPEATED_SCROLL_THRESHOLD_MILLIS: u128 = 300; - const SCROLL_SPEED_START: f32 = 0.1_f32; - const SCROLL_SPEED_MAX: f32 = 10_f32; - const SCROLL_SPEED_MULTIPLIER: f32 = 1.05_f32; + fn update_scroll_speed(&mut self) { + const REPEATED_SCROLL_THRESHOLD_MILLIS: u128 = 300; + const SCROLL_SPEED_START: f32 = 0.1_f32; + const SCROLL_SPEED_MAX: f32 = 10_f32; + const SCROLL_SPEED_MULTIPLIER: f32 = 1.05_f32; - let now = Instant::now(); + let now = Instant::now(); - let since_last_scroll = - now.duration_since(self.scroll_state.0); + let since_last_scroll = + now.duration_since(self.scroll_state.0); - self.scroll_state.0 = now; + self.scroll_state.0 = now; - let speed = if since_last_scroll.as_millis() - < REPEATED_SCROLL_THRESHOLD_MILLIS - { - self.scroll_state.1 * SCROLL_SPEED_MULTIPLIER - } else { - SCROLL_SPEED_START - }; + let speed = if since_last_scroll.as_millis() + < REPEATED_SCROLL_THRESHOLD_MILLIS + { + self.scroll_state.1 * SCROLL_SPEED_MULTIPLIER + } else { + SCROLL_SPEED_START + }; - self.scroll_state.1 = speed.min(SCROLL_SPEED_MAX); - } + self.scroll_state.1 = speed.min(SCROLL_SPEED_MAX); + } - fn is_marked(&self, id: &CommitId) -> Option { - if self.marked.is_empty() { - None - } else { - let found = self.marked.iter().any(|entry| entry == id); - Some(found) - } - } + fn is_marked(&self, id: &CommitId) -> Option { + if self.marked.is_empty() { + None + } else { + let found = self.marked.iter().any(|entry| entry == id); + Some(found) + } + } - fn get_entry_to_add<'a>( - e: &'a LogEntry, - selected: bool, - tags: Option, - theme: &Theme, - width: usize, - now: DateTime, - marked: Option, - ) -> Spans<'a> { - let mut txt: Vec = Vec::new(); - txt.reserve( - ELEMENTS_PER_LINE + if marked.is_some() { 2 } else { 0 }, - ); + fn get_entry_to_add<'a>( + e: &'a LogEntry, + selected: bool, + tags: Option, + theme: &Theme, + width: usize, + now: DateTime, + marked: Option, + ) -> Spans<'a> { + let mut txt: Vec = Vec::new(); + txt.reserve( + ELEMENTS_PER_LINE + if marked.is_some() { 2 } else { 0 }, + ); - let splitter_txt = Cow::from(" "); - let splitter = - Span::styled(splitter_txt, theme.text(true, selected)); + let splitter_txt = Cow::from(" "); + let splitter = + Span::styled(splitter_txt, theme.text(true, selected)); - // marked - if let Some(marked) = marked { - txt.push(Span::styled( - Cow::from(if marked { "X" } else { " " }), - theme.text(true, selected), - )); - txt.push(splitter.clone()); - } + // marked + if let Some(marked) = marked { + txt.push(Span::styled( + Cow::from(if marked { "X" } else { " " }), + theme.text(true, selected), + )); + txt.push(splitter.clone()); + } - // commit hash - txt.push(Span::styled( - Cow::from(e.hash_short.as_str()), - theme.commit_hash(selected), - )); + // commit hash + txt.push(Span::styled( + Cow::from(e.hash_short.as_str()), + theme.commit_hash(selected), + )); - txt.push(splitter.clone()); + txt.push(splitter.clone()); - // commit timestamp - txt.push(Span::styled( - Cow::from(e.time_to_string(now)), - theme.commit_time(selected), - )); + // commit timestamp + txt.push(Span::styled( + Cow::from(e.time_to_string(now)), + theme.commit_time(selected), + )); - txt.push(splitter.clone()); + txt.push(splitter.clone()); - let author_width = - (width.saturating_sub(19) / 3).max(3).min(20); - let author = string_width_align(&e.author, author_width); + let author_width = + (width.saturating_sub(19) / 3).max(3).min(20); + let author = string_width_align(&e.author, author_width); - // commit author - txt.push(Span::styled::( - author, - theme.commit_author(selected), - )); + // commit author + txt.push(Span::styled::( + author, + theme.commit_author(selected), + )); - txt.push(splitter.clone()); + txt.push(splitter.clone()); - // commit tags - txt.push(Span::styled( - Cow::from(if let Some(tags) = tags { - format!(" {}", tags) - } else { - String::from("") - }), - theme.tags(selected), - )); + // commit tags + txt.push(Span::styled( + Cow::from(if let Some(tags) = tags { + format!(" {}", tags) + } else { + String::from("") + }), + theme.tags(selected), + )); - txt.push(splitter); + txt.push(splitter); - // commit msg - txt.push(Span::styled( - Cow::from(e.msg.as_str()), - theme.text(true, selected), - )); - Spans::from(txt) - } + // commit msg + txt.push(Span::styled( + Cow::from(e.msg.as_str()), + theme.text(true, selected), + )); + Spans::from(txt) + } - fn get_text(&self, height: usize, width: usize) -> Vec { - let selection = self.relative_selection(); + fn get_text(&self, height: usize, width: usize) -> Vec { + let selection = self.relative_selection(); - let mut txt: Vec = Vec::with_capacity(height); + let mut txt: Vec = Vec::with_capacity(height); - let now = Local::now(); + let now = Local::now(); - let any_marked = !self.marked.is_empty(); + let any_marked = !self.marked.is_empty(); - for (idx, e) in self - .items - .iter() - .skip(self.scroll_top.get()) - .take(height) - .enumerate() - { - let tags = self - .tags - .as_ref() - .and_then(|t| t.get(&e.id)) - .map(|tags| tags.join(" ")); + for (idx, e) in self + .items + .iter() + .skip(self.scroll_top.get()) + .take(height) + .enumerate() + { + let tags = self + .tags + .as_ref() + .and_then(|t| t.get(&e.id)) + .map(|tags| tags.join(" ")); - let marked = if any_marked { - self.is_marked(&e.id) - } else { - None - }; + let marked = if any_marked { + self.is_marked(&e.id) + } else { + None + }; - txt.push(Self::get_entry_to_add( - e, - idx + self.scroll_top.get() == selection, - tags, - &self.theme, - width, - now, - marked, - )); - } + txt.push(Self::get_entry_to_add( + e, + idx + self.scroll_top.get() == selection, + tags, + &self.theme, + width, + now, + marked, + )); + } - txt - } + txt + } - #[allow(clippy::missing_const_for_fn)] - fn relative_selection(&self) -> usize { - self.selection.saturating_sub(self.items.index_offset()) - } + #[allow(clippy::missing_const_for_fn)] + fn relative_selection(&self) -> usize { + self.selection.saturating_sub(self.items.index_offset()) + } - pub fn select_entry(&mut self, position: usize) { - self.selection = position; - } + pub fn select_entry(&mut self, position: usize) { + self.selection = position; + } } impl DrawableComponent for CommitList { - fn draw( - &self, - f: &mut Frame, - area: Rect, - ) -> Result<()> { - let current_size = ( - area.width.saturating_sub(2), - area.height.saturating_sub(2), - ); - self.current_size.set(current_size); + fn draw( + &self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { + let current_size = ( + area.width.saturating_sub(2), + area.height.saturating_sub(2), + ); + self.current_size.set(current_size); - let height_in_lines = self.current_size.get().1 as usize; - let selection = self.relative_selection(); + let height_in_lines = self.current_size.get().1 as usize; + let selection = self.relative_selection(); - self.scroll_top.set(calc_scroll_top( - self.scroll_top.get(), - height_in_lines, - selection, - )); + self.scroll_top.set(calc_scroll_top( + self.scroll_top.get(), + height_in_lines, + selection, + )); - let branch_post_fix = - self.branch.as_ref().map(|b| format!("- {{{}}}", b)); + let branch_post_fix = + self.branch.as_ref().map(|b| format!("- {{{}}}", b)); - let title = format!( - "{} {}/{} {}", - self.title, - self.count_total.saturating_sub(self.selection), - self.count_total, - branch_post_fix.as_deref().unwrap_or(""), - ); + let title = format!( + "{} {}/{} {}", + self.title, + self.count_total.saturating_sub(self.selection), + self.count_total, + branch_post_fix.as_deref().unwrap_or(""), + ); - f.render_widget( - Paragraph::new( - self.get_text( - height_in_lines, - current_size.0 as usize, - ), - ) - .block( - Block::default() - .borders(Borders::ALL) - .title(Span::styled( - title.as_str(), - self.theme.title(true), - )) - .border_style(self.theme.block(true)), - ) - .alignment(Alignment::Left), - area, - ); + f.render_widget( + Paragraph::new( + self.get_text( + height_in_lines, + current_size.0 as usize, + ), + ) + .block( + Block::default() + .borders(Borders::ALL) + .title(Span::styled( + title.as_str(), + self.theme.title(true), + )) + .border_style(self.theme.block(true)), + ) + .alignment(Alignment::Left), + area, + ); - Ok(()) - } + Ok(()) + } } impl Component for CommitList { - fn event(&mut self, ev: Event) -> Result { - if let Event::Key(k) = ev { - let selection_changed = if k == self.key_config.move_up { - self.move_selection(ScrollType::Up)? - } else if k == self.key_config.move_down { - self.move_selection(ScrollType::Down)? - } else if k == self.key_config.shift_up - || k == self.key_config.home - { - self.move_selection(ScrollType::Home)? - } else if k == self.key_config.shift_down - || k == self.key_config.end - { - self.move_selection(ScrollType::End)? - } else if k == self.key_config.page_up { - self.move_selection(ScrollType::PageUp)? - } else if k == self.key_config.page_down { - self.move_selection(ScrollType::PageDown)? - } else if k == self.key_config.log_mark_commit { - self.mark(); - true - } else { - false - }; - return Ok(selection_changed.into()); - } + fn event(&mut self, ev: Event) -> Result { + if let Event::Key(k) = ev { + let selection_changed = if k == self.key_config.move_up { + self.move_selection(ScrollType::Up)? + } else if k == self.key_config.move_down { + self.move_selection(ScrollType::Down)? + } else if k == self.key_config.shift_up + || k == self.key_config.home + { + self.move_selection(ScrollType::Home)? + } else if k == self.key_config.shift_down + || k == self.key_config.end + { + self.move_selection(ScrollType::End)? + } else if k == self.key_config.page_up { + self.move_selection(ScrollType::PageUp)? + } else if k == self.key_config.page_down { + self.move_selection(ScrollType::PageDown)? + } else if k == self.key_config.log_mark_commit { + self.mark(); + true + } else { + false + }; + return Ok(selection_changed.into()); + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn commands( - &self, - out: &mut Vec, - _force_all: bool, - ) -> CommandBlocking { - out.push(CommandInfo::new( - strings::commands::scroll(&self.key_config), - self.selected_entry().is_some(), - true, - )); - CommandBlocking::PassingOn - } + fn commands( + &self, + out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + out.push(CommandInfo::new( + strings::commands::scroll(&self.key_config), + self.selected_entry().is_some(), + true, + )); + CommandBlocking::PassingOn + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_string_width_align() { - assert_eq!(string_width_align("123", 3), "123"); - assert_eq!(string_width_align("123", 2), ".."); - assert_eq!(string_width_align("123", 3), "123"); - assert_eq!(string_width_align("12345", 6), "12345 "); - assert_eq!(string_width_align("1234556", 4), "12.."); - } + #[test] + fn test_string_width_align() { + assert_eq!(string_width_align("123", 3), "123"); + assert_eq!(string_width_align("123", 2), ".."); + assert_eq!(string_width_align("123", 3), "123"); + assert_eq!(string_width_align("12345", 6), "12345 "); + assert_eq!(string_width_align("1234556", 4), "12.."); + } - #[test] - fn test_string_width_align_unicode() { - assert_eq!(string_width_align("äste", 3), "ä.."); - assert_eq!( - string_width_align("wüsten äste", 10), - "wüsten ä.." - ); - assert_eq!( - string_width_align("Jon Grythe Stødle", 19), - "Jon Grythe Stødle " - ); - } + #[test] + fn test_string_width_align_unicode() { + assert_eq!(string_width_align("äste", 3), "ä.."); + assert_eq!( + string_width_align("wüsten äste", 10), + "wüsten ä.." + ); + assert_eq!( + string_width_align("Jon Grythe Stødle", 19), + "Jon Grythe Stødle " + ); + } } diff --git a/src/components/create_branch.rs b/src/components/create_branch.rs index 485ec99f..c6bb2e88 100644 --- a/src/components/create_branch.rs +++ b/src/components/create_branch.rs @@ -1,176 +1,176 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - keys::SharedKeyConfig, - queue::{InternalEvent, NeedsUpdate, Queue}, - strings, - ui::style::SharedTheme, + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{sync, CWD}; use crossterm::event::Event; use easy_cast::Cast; use tui::{ - backend::Backend, layout::Rect, widgets::Paragraph, Frame, + backend::Backend, layout::Rect, widgets::Paragraph, Frame, }; pub struct CreateBranchComponent { - input: TextInputComponent, - queue: Queue, - key_config: SharedKeyConfig, - theme: SharedTheme, + input: TextInputComponent, + queue: Queue, + key_config: SharedKeyConfig, + theme: SharedTheme, } impl DrawableComponent for CreateBranchComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.is_visible() { - self.input.draw(f, rect)?; - self.draw_warnings(f); - } + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.is_visible() { + self.input.draw(f, rect)?; + self.draw_warnings(f); + } - Ok(()) - } + Ok(()) + } } impl Component for CreateBranchComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - self.input.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + self.input.commands(out, force_all); - out.push(CommandInfo::new( - strings::commands::create_branch_confirm_msg( - &self.key_config, - ), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::create_branch_confirm_msg( + &self.key_config, + ), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { - if self.input.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if self.input.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if let Event::Key(e) = ev { - if e == self.key_config.enter { - self.create_branch(); - } + if let Event::Key(e) = ev { + if e == self.key_config.enter { + self.create_branch(); + } - return Ok(EventState::Consumed); - } - } - Ok(EventState::NotConsumed) - } + return Ok(EventState::Consumed); + } + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.input.is_visible() - } + fn is_visible(&self) -> bool { + self.input.is_visible() + } - fn hide(&mut self) { - self.input.hide(); - } + fn hide(&mut self) { + self.input.hide(); + } - fn show(&mut self) -> Result<()> { - self.input.show()?; + fn show(&mut self) -> Result<()> { + self.input.show()?; - Ok(()) - } + Ok(()) + } } impl CreateBranchComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue, - input: TextInputComponent::new( - theme.clone(), - key_config.clone(), - &strings::create_branch_popup_title(&key_config), - &strings::create_branch_popup_msg(&key_config), - true, - ), - theme, - key_config, - } - } + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue, + input: TextInputComponent::new( + theme.clone(), + key_config.clone(), + &strings::create_branch_popup_title(&key_config), + &strings::create_branch_popup_msg(&key_config), + true, + ), + theme, + key_config, + } + } - /// - pub fn open(&mut self) -> Result<()> { - self.show()?; + /// + pub fn open(&mut self) -> Result<()> { + self.show()?; - Ok(()) - } + Ok(()) + } - /// - pub fn create_branch(&mut self) { - let res = - sync::create_branch(CWD, self.input.get_text().as_str()); + /// + pub fn create_branch(&mut self) { + let res = + sync::create_branch(CWD, self.input.get_text().as_str()); - self.input.clear(); - self.hide(); + self.input.clear(); + self.hide(); - match res { - Ok(_) => { - self.queue.push(InternalEvent::Update( - NeedsUpdate::BRANCHES, - )); - } - Err(e) => { - log::error!("create branch: {}", e,); - self.queue.push(InternalEvent::ShowErrorMsg( - format!("create branch error:\n{}", e,), - )); - } - } - } + match res { + Ok(_) => { + self.queue.push(InternalEvent::Update( + NeedsUpdate::BRANCHES, + )); + } + Err(e) => { + log::error!("create branch: {}", e,); + self.queue.push(InternalEvent::ShowErrorMsg( + format!("create branch error:\n{}", e,), + )); + } + } + } - fn draw_warnings(&self, f: &mut Frame) { - let current_text = self.input.get_text(); + fn draw_warnings(&self, f: &mut Frame) { + let current_text = self.input.get_text(); - if !current_text.is_empty() { - let valid = sync::validate_branch_name(current_text) - .unwrap_or_default(); + if !current_text.is_empty() { + let valid = sync::validate_branch_name(current_text) + .unwrap_or_default(); - if !valid { - let msg = strings::branch_name_invalid(); - let msg_length: u16 = msg.len().cast(); - let w = Paragraph::new(msg) - .style(self.theme.text_danger()); + if !valid { + let msg = strings::branch_name_invalid(); + let msg_length: u16 = msg.len().cast(); + let w = Paragraph::new(msg) + .style(self.theme.text_danger()); - let rect = { - let mut rect = self.input.get_area(); - rect.y += rect.height.saturating_sub(1); - rect.height = 1; - let offset = - rect.width.saturating_sub(msg_length + 1); - rect.width = - rect.width.saturating_sub(offset + 1); - rect.x += offset; + let rect = { + let mut rect = self.input.get_area(); + rect.y += rect.height.saturating_sub(1); + rect.height = 1; + let offset = + rect.width.saturating_sub(msg_length + 1); + rect.width = + rect.width.saturating_sub(offset + 1); + rect.x += offset; - rect - }; + rect + }; - f.render_widget(w, rect); - } - } - } + f.render_widget(w, rect); + } + } + } } diff --git a/src/components/cred.rs b/src/components/cred.rs index ecd277d4..66725509 100644 --- a/src/components/cred.rs +++ b/src/components/cred.rs @@ -6,164 +6,164 @@ use asyncgit::sync::cred::BasicAuthCredential; use crate::components::{EventState, InputType, TextInputComponent}; use crate::{ - components::{ - visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, - }, - keys::SharedKeyConfig, - strings, - ui::style::SharedTheme, + components::{ + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, + }, + keys::SharedKeyConfig, + strings, + ui::style::SharedTheme, }; /// pub struct CredComponent { - visible: bool, - key_config: SharedKeyConfig, - input_username: TextInputComponent, - input_password: TextInputComponent, - cred: BasicAuthCredential, + visible: bool, + key_config: SharedKeyConfig, + input_username: TextInputComponent, + input_password: TextInputComponent, + cred: BasicAuthCredential, } impl CredComponent { - /// - pub fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - visible: false, - input_username: TextInputComponent::new( - theme.clone(), - key_config.clone(), - &strings::username_popup_title(&key_config), - &strings::username_popup_msg(&key_config), - false, - ) - .with_input_type(InputType::Singleline), - input_password: TextInputComponent::new( - theme, - key_config.clone(), - &strings::password_popup_title(&key_config), - &strings::password_popup_msg(&key_config), - false, - ) - .with_input_type(InputType::Password), - key_config, - cred: BasicAuthCredential::new(None, None), - } - } + /// + pub fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + visible: false, + input_username: TextInputComponent::new( + theme.clone(), + key_config.clone(), + &strings::username_popup_title(&key_config), + &strings::username_popup_msg(&key_config), + false, + ) + .with_input_type(InputType::Singleline), + input_password: TextInputComponent::new( + theme, + key_config.clone(), + &strings::password_popup_title(&key_config), + &strings::password_popup_msg(&key_config), + false, + ) + .with_input_type(InputType::Password), + key_config, + cred: BasicAuthCredential::new(None, None), + } + } - pub fn set_cred(&mut self, cred: BasicAuthCredential) { - self.cred = cred; - } + pub fn set_cred(&mut self, cred: BasicAuthCredential) { + self.cred = cred; + } - pub const fn get_cred(&self) -> &BasicAuthCredential { - &self.cred - } + pub const fn get_cred(&self) -> &BasicAuthCredential { + &self.cred + } } impl DrawableComponent for CredComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.visible { - self.input_username.draw(f, rect)?; - self.input_password.draw(f, rect)?; - } - Ok(()) - } + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.visible { + self.input_username.draw(f, rect)?; + self.input_password.draw(f, rect)?; + } + Ok(()) + } } impl Component for CredComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - if !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + if !force_all { + out.clear(); + } - out.push(CommandInfo::new( - strings::commands::validate_msg(&self.key_config), - true, - true, - )); - out.push(CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::validate_msg(&self.key_config), + true, + true, + )); + out.push(CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - return Ok(EventState::Consumed); - } - if self.input_username.event(ev)?.is_consumed() - || self.input_password.event(ev)?.is_consumed() - { - return Ok(EventState::Consumed); - } else if e == self.key_config.enter { - if self.input_username.is_visible() { - self.cred = BasicAuthCredential::new( - Some( - self.input_username - .get_text() - .clone(), - ), - None, - ); - self.input_username.hide(); - self.input_password.show()?; - } else if self.input_password.is_visible() { - self.cred = BasicAuthCredential::new( - self.cred.username.clone(), - Some( - self.input_password - .get_text() - .clone(), - ), - ); - self.input_password.hide(); - self.input_password.clear(); - return Ok(EventState::NotConsumed); - } else { - self.hide(); - } - } - } - return Ok(EventState::Consumed); - } - Ok(EventState::NotConsumed) - } + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + return Ok(EventState::Consumed); + } + if self.input_username.event(ev)?.is_consumed() + || self.input_password.event(ev)?.is_consumed() + { + return Ok(EventState::Consumed); + } else if e == self.key_config.enter { + if self.input_username.is_visible() { + self.cred = BasicAuthCredential::new( + Some( + self.input_username + .get_text() + .clone(), + ), + None, + ); + self.input_username.hide(); + self.input_password.show()?; + } else if self.input_password.is_visible() { + self.cred = BasicAuthCredential::new( + self.cred.username.clone(), + Some( + self.input_password + .get_text() + .clone(), + ), + ); + self.input_password.hide(); + self.input_password.clear(); + return Ok(EventState::NotConsumed); + } else { + self.hide(); + } + } + } + return Ok(EventState::Consumed); + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.cred = BasicAuthCredential::new(None, None); - self.visible = false; - } + fn hide(&mut self) { + self.cred = BasicAuthCredential::new(None, None); + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; - if self.cred.username.is_none() { - self.input_username.show() - } else if self.cred.password.is_none() { - self.input_password.show() - } else { - Ok(()) - } - } + fn show(&mut self) -> Result<()> { + self.visible = true; + if self.cred.username.is_none() { + self.input_username.show() + } else if self.cred.password.is_none() { + self.input_password.show() + } else { + Ok(()) + } + } } diff --git a/src/components/diff.rs b/src/components/diff.rs index 8b715599..c46ab2d0 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -1,801 +1,801 @@ use super::{ - utils::scroll_vertical::VerticalScroll, CommandBlocking, - Direction, DrawableComponent, ScrollType, + utils::scroll_vertical::VerticalScroll, CommandBlocking, + Direction, DrawableComponent, ScrollType, }; use crate::{ - components::{CommandInfo, Component, EventState}, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, - strings, try_or_popup, - ui::style::SharedTheme, + components::{CommandInfo, Component, EventState}, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, + strings, try_or_popup, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - hash, - sync::{self, diff::DiffLinePosition}, - DiffLine, DiffLineType, FileDiff, CWD, + hash, + sync::{self, diff::DiffLinePosition}, + DiffLine, DiffLineType, FileDiff, CWD, }; use bytesize::ByteSize; use crossterm::event::Event; use std::{borrow::Cow, cell::Cell, cmp, path::Path}; use tui::{ - backend::Backend, - layout::Rect, - symbols, - text::{Span, Spans}, - widgets::{Block, Borders, Paragraph}, - Frame, + backend::Backend, + layout::Rect, + symbols, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, + Frame, }; #[derive(Default)] struct Current { - path: String, - is_stage: bool, - hash: u64, + path: String, + is_stage: bool, + hash: u64, } /// #[derive(Clone, Copy)] enum Selection { - Single(usize), - Multiple(usize, usize), + Single(usize), + Multiple(usize, usize), } impl Selection { - const fn get_start(&self) -> usize { - match self { - Self::Single(start) | Self::Multiple(start, _) => *start, - } - } + const fn get_start(&self) -> usize { + match self { + Self::Single(start) | Self::Multiple(start, _) => *start, + } + } - const fn get_end(&self) -> usize { - match self { - Self::Single(end) | Self::Multiple(_, end) => *end, - } - } + const fn get_end(&self) -> usize { + match self { + Self::Single(end) | Self::Multiple(_, end) => *end, + } + } - fn get_top(&self) -> usize { - match self { - Self::Single(start) => *start, - Self::Multiple(start, end) => cmp::min(*start, *end), - } - } + fn get_top(&self) -> usize { + match self { + Self::Single(start) => *start, + Self::Multiple(start, end) => cmp::min(*start, *end), + } + } - fn get_bottom(&self) -> usize { - match self { - Self::Single(start) => *start, - Self::Multiple(start, end) => cmp::max(*start, *end), - } - } + fn get_bottom(&self) -> usize { + match self { + Self::Single(start) => *start, + Self::Multiple(start, end) => cmp::max(*start, *end), + } + } - fn modify(&mut self, direction: Direction, max: usize) { - let start = self.get_start(); - let old_end = self.get_end(); + fn modify(&mut self, direction: Direction, max: usize) { + let start = self.get_start(); + let old_end = self.get_end(); - *self = match direction { - Direction::Up => { - Self::Multiple(start, old_end.saturating_sub(1)) - } + *self = match direction { + Direction::Up => { + Self::Multiple(start, old_end.saturating_sub(1)) + } - Direction::Down => { - Self::Multiple(start, cmp::min(old_end + 1, max)) - } - }; - } + Direction::Down => { + Self::Multiple(start, cmp::min(old_end + 1, max)) + } + }; + } - fn contains(&self, index: usize) -> bool { - match self { - Self::Single(start) => index == *start, - Self::Multiple(start, end) => { - if start <= end { - *start <= index && index <= *end - } else { - *end <= index && index <= *start - } - } - } - } + fn contains(&self, index: usize) -> bool { + match self { + Self::Single(start) => index == *start, + Self::Multiple(start, end) => { + if start <= end { + *start <= index && index <= *end + } else { + *end <= index && index <= *start + } + } + } + } } /// pub struct DiffComponent { - diff: Option, - pending: bool, - selection: Selection, - selected_hunk: Option, - current_size: Cell<(u16, u16)>, - focused: bool, - current: Current, - scroll: VerticalScroll, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - is_immutable: bool, + diff: Option, + pending: bool, + selection: Selection, + selected_hunk: Option, + current_size: Cell<(u16, u16)>, + focused: bool, + current: Current, + scroll: VerticalScroll, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + is_immutable: bool, } impl DiffComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - is_immutable: bool, - ) -> Self { - Self { - focused: false, - queue, - current: Current::default(), - pending: false, - selected_hunk: None, - diff: None, - current_size: Cell::new((0, 0)), - selection: Selection::Single(0), - scroll: VerticalScroll::new(), - theme, - key_config, - is_immutable, - } - } - /// - fn can_scroll(&self) -> bool { - self.diff - .as_ref() - .map(|diff| diff.lines > 1) - .unwrap_or_default() - } - /// - pub fn current(&self) -> (String, bool) { - (self.current.path.clone(), self.current.is_stage) - } - /// - pub fn clear(&mut self, pending: bool) { - self.current = Current::default(); - self.diff = None; - self.scroll.reset(); - self.selection = Selection::Single(0); - self.selected_hunk = None; - self.pending = pending; - } - /// - pub fn update( - &mut self, - path: String, - is_stage: bool, - diff: FileDiff, - ) { - self.pending = false; + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + is_immutable: bool, + ) -> Self { + Self { + focused: false, + queue, + current: Current::default(), + pending: false, + selected_hunk: None, + diff: None, + current_size: Cell::new((0, 0)), + selection: Selection::Single(0), + scroll: VerticalScroll::new(), + theme, + key_config, + is_immutable, + } + } + /// + fn can_scroll(&self) -> bool { + self.diff + .as_ref() + .map(|diff| diff.lines > 1) + .unwrap_or_default() + } + /// + pub fn current(&self) -> (String, bool) { + (self.current.path.clone(), self.current.is_stage) + } + /// + pub fn clear(&mut self, pending: bool) { + self.current = Current::default(); + self.diff = None; + self.scroll.reset(); + self.selection = Selection::Single(0); + self.selected_hunk = None; + self.pending = pending; + } + /// + pub fn update( + &mut self, + path: String, + is_stage: bool, + diff: FileDiff, + ) { + self.pending = false; - let hash = hash(&diff); + let hash = hash(&diff); - if self.current.hash != hash { - let reset_selection = self.current.path != path; + if self.current.hash != hash { + let reset_selection = self.current.path != path; - self.current = Current { - path, - is_stage, - hash, - }; + self.current = Current { + path, + is_stage, + hash, + }; - self.diff = Some(diff); + self.diff = Some(diff); - if reset_selection { - self.scroll.reset(); - self.selection = Selection::Single(0); - self.update_selection(0); - } else { - let old_selection = match self.selection { - Selection::Single(line) => line, - Selection::Multiple(start, _) => start, - }; - self.update_selection(old_selection); - } - } - } + if reset_selection { + self.scroll.reset(); + self.selection = Selection::Single(0); + self.update_selection(0); + } else { + let old_selection = match self.selection { + Selection::Single(line) => line, + Selection::Multiple(start, _) => start, + }; + self.update_selection(old_selection); + } + } + } - fn move_selection(&mut self, move_type: ScrollType) { - if let Some(diff) = &self.diff { - let max = diff.lines.saturating_sub(1) as usize; + fn move_selection(&mut self, move_type: ScrollType) { + if let Some(diff) = &self.diff { + let max = diff.lines.saturating_sub(1) as usize; - let new_start = match move_type { - ScrollType::Down => { - self.selection.get_bottom().saturating_add(1) - } - ScrollType::Up => { - self.selection.get_top().saturating_sub(1) - } - ScrollType::Home => 0, - ScrollType::End => max, - ScrollType::PageDown => { - self.selection.get_bottom().saturating_add( - self.current_size.get().1.saturating_sub(1) - as usize, - ) - } - ScrollType::PageUp => { - self.selection.get_top().saturating_sub( - self.current_size.get().1.saturating_sub(1) - as usize, - ) - } - }; + let new_start = match move_type { + ScrollType::Down => { + self.selection.get_bottom().saturating_add(1) + } + ScrollType::Up => { + self.selection.get_top().saturating_sub(1) + } + ScrollType::Home => 0, + ScrollType::End => max, + ScrollType::PageDown => { + self.selection.get_bottom().saturating_add( + self.current_size.get().1.saturating_sub(1) + as usize, + ) + } + ScrollType::PageUp => { + self.selection.get_top().saturating_sub( + self.current_size.get().1.saturating_sub(1) + as usize, + ) + } + }; - self.update_selection(new_start); - } - } + self.update_selection(new_start); + } + } - fn update_selection(&mut self, new_start: usize) { - if let Some(diff) = &self.diff { - let max = diff.lines.saturating_sub(1) as usize; - let new_start = cmp::min(max, new_start); - self.selection = Selection::Single(new_start); - self.selected_hunk = - Self::find_selected_hunk(diff, new_start); - } - } + fn update_selection(&mut self, new_start: usize) { + if let Some(diff) = &self.diff { + let max = diff.lines.saturating_sub(1) as usize; + let new_start = cmp::min(max, new_start); + self.selection = Selection::Single(new_start); + self.selected_hunk = + Self::find_selected_hunk(diff, new_start); + } + } - fn lines_count(&self) -> usize { - self.diff.as_ref().map_or(0, |diff| diff.lines) - } + fn lines_count(&self) -> usize { + self.diff.as_ref().map_or(0, |diff| diff.lines) + } - fn modify_selection(&mut self, direction: Direction) { - if self.diff.is_some() { - self.selection.modify(direction, self.lines_count()); - } - } + fn modify_selection(&mut self, direction: Direction) { + if self.diff.is_some() { + self.selection.modify(direction, self.lines_count()); + } + } - fn copy_selection(&self) { - if let Some(diff) = &self.diff { - let lines_to_copy: Vec<&str> = - diff.hunks - .iter() - .flat_map(|hunk| hunk.lines.iter()) - .enumerate() - .filter_map(|(i, line)| { - if self.selection.contains(i) { - Some(line.content.trim_matches(|c| { - c == '\n' || c == '\r' - })) - } else { - None - } - }) - .collect(); + fn copy_selection(&self) { + if let Some(diff) = &self.diff { + let lines_to_copy: Vec<&str> = + diff.hunks + .iter() + .flat_map(|hunk| hunk.lines.iter()) + .enumerate() + .filter_map(|(i, line)| { + if self.selection.contains(i) { + Some(line.content.trim_matches(|c| { + c == '\n' || c == '\r' + })) + } else { + None + } + }) + .collect(); - try_or_popup!( - self, - "copy to clipboard error:", - crate::clipboard::copy_string( - &lines_to_copy.join("\n") - ) - ); - } - } + try_or_popup!( + self, + "copy to clipboard error:", + crate::clipboard::copy_string( + &lines_to_copy.join("\n") + ) + ); + } + } - fn find_selected_hunk( - diff: &FileDiff, - line_selected: usize, - ) -> Option { - let mut line_cursor = 0_usize; - for (i, hunk) in diff.hunks.iter().enumerate() { - let hunk_len = hunk.lines.len(); - let hunk_min = line_cursor; - let hunk_max = line_cursor + hunk_len; + fn find_selected_hunk( + diff: &FileDiff, + line_selected: usize, + ) -> Option { + let mut line_cursor = 0_usize; + for (i, hunk) in diff.hunks.iter().enumerate() { + let hunk_len = hunk.lines.len(); + let hunk_min = line_cursor; + let hunk_max = line_cursor + hunk_len; - let hunk_selected = - hunk_min <= line_selected && hunk_max > line_selected; + let hunk_selected = + hunk_min <= line_selected && hunk_max > line_selected; - if hunk_selected { - return Some(i); - } + if hunk_selected { + return Some(i); + } - line_cursor += hunk_len; - } + line_cursor += hunk_len; + } - None - } + None + } - fn get_text(&self, width: u16, height: u16) -> Vec { - let mut res: Vec = Vec::new(); - if let Some(diff) = &self.diff { - if diff.hunks.is_empty() { - let is_positive = diff.size_delta >= 0; - let delta_byte_size = - ByteSize::b(diff.size_delta.abs() as u64); - let sign = if is_positive { "+" } else { "-" }; - res.extend(vec![Spans::from(vec![ - Span::raw(Cow::from("size: ")), - Span::styled( - Cow::from(format!( - "{}", - ByteSize::b(diff.sizes.0) - )), - self.theme.text(false, false), - ), - Span::raw(Cow::from(" -> ")), - Span::styled( - Cow::from(format!( - "{}", - ByteSize::b(diff.sizes.1) - )), - self.theme.text(false, false), - ), - Span::raw(Cow::from(" (")), - Span::styled( - Cow::from(format!( - "{}{:}", - sign, delta_byte_size - )), - self.theme.diff_line( - if is_positive { - DiffLineType::Add - } else { - DiffLineType::Delete - }, - false, - ), - ), - Span::raw(Cow::from(")")), - ])]); - } else { - let min = self.scroll.get_top(); - let max = min + height as usize; + fn get_text(&self, width: u16, height: u16) -> Vec { + let mut res: Vec = Vec::new(); + if let Some(diff) = &self.diff { + if diff.hunks.is_empty() { + let is_positive = diff.size_delta >= 0; + let delta_byte_size = + ByteSize::b(diff.size_delta.abs() as u64); + let sign = if is_positive { "+" } else { "-" }; + res.extend(vec![Spans::from(vec![ + Span::raw(Cow::from("size: ")), + Span::styled( + Cow::from(format!( + "{}", + ByteSize::b(diff.sizes.0) + )), + self.theme.text(false, false), + ), + Span::raw(Cow::from(" -> ")), + Span::styled( + Cow::from(format!( + "{}", + ByteSize::b(diff.sizes.1) + )), + self.theme.text(false, false), + ), + Span::raw(Cow::from(" (")), + Span::styled( + Cow::from(format!( + "{}{:}", + sign, delta_byte_size + )), + self.theme.diff_line( + if is_positive { + DiffLineType::Add + } else { + DiffLineType::Delete + }, + false, + ), + ), + Span::raw(Cow::from(")")), + ])]); + } else { + let min = self.scroll.get_top(); + let max = min + height as usize; - let mut line_cursor = 0_usize; - let mut lines_added = 0_usize; + let mut line_cursor = 0_usize; + let mut lines_added = 0_usize; - for (i, hunk) in diff.hunks.iter().enumerate() { - let hunk_selected = self.focused() - && self - .selected_hunk - .map_or(false, |s| s == i); + for (i, hunk) in diff.hunks.iter().enumerate() { + let hunk_selected = self.focused() + && self + .selected_hunk + .map_or(false, |s| s == i); - if lines_added >= height as usize { - break; - } + if lines_added >= height as usize { + break; + } - let hunk_len = hunk.lines.len(); - let hunk_min = line_cursor; - let hunk_max = line_cursor + hunk_len; + let hunk_len = hunk.lines.len(); + let hunk_min = line_cursor; + let hunk_max = line_cursor + hunk_len; - if Self::hunk_visible( - hunk_min, hunk_max, min, max, - ) { - for (i, line) in hunk.lines.iter().enumerate() - { - if line_cursor >= min - && line_cursor <= max - { - res.push(Self::get_line_to_add( - width, - line, - self.focused() - && self - .selection - .contains(line_cursor), - hunk_selected, - i == hunk_len as usize - 1, - &self.theme, - )); - lines_added += 1; - } + if Self::hunk_visible( + hunk_min, hunk_max, min, max, + ) { + for (i, line) in hunk.lines.iter().enumerate() + { + if line_cursor >= min + && line_cursor <= max + { + res.push(Self::get_line_to_add( + width, + line, + self.focused() + && self + .selection + .contains(line_cursor), + hunk_selected, + i == hunk_len as usize - 1, + &self.theme, + )); + lines_added += 1; + } - line_cursor += 1; - } - } else { - line_cursor += hunk_len; - } - } - } - } - res - } + line_cursor += 1; + } + } else { + line_cursor += hunk_len; + } + } + } + } + res + } - fn get_line_to_add<'a>( - width: u16, - line: &'a DiffLine, - selected: bool, - selected_hunk: bool, - end_of_hunk: bool, - theme: &SharedTheme, - ) -> Spans<'a> { - let style = theme.diff_hunk_marker(selected_hunk); + fn get_line_to_add<'a>( + width: u16, + line: &'a DiffLine, + selected: bool, + selected_hunk: bool, + end_of_hunk: bool, + theme: &SharedTheme, + ) -> Spans<'a> { + let style = theme.diff_hunk_marker(selected_hunk); - let left_side_of_line = if end_of_hunk { - Span::styled(Cow::from(symbols::line::BOTTOM_LEFT), style) - } else { - match line.line_type { - DiffLineType::Header => Span::styled( - Cow::from(symbols::line::TOP_LEFT), - style, - ), - _ => Span::styled( - Cow::from(symbols::line::VERTICAL), - style, - ), - } - }; + let left_side_of_line = if end_of_hunk { + Span::styled(Cow::from(symbols::line::BOTTOM_LEFT), style) + } else { + match line.line_type { + DiffLineType::Header => Span::styled( + Cow::from(symbols::line::TOP_LEFT), + style, + ), + _ => Span::styled( + Cow::from(symbols::line::VERTICAL), + style, + ), + } + }; - let trimmed = - line.content.trim_matches(|c| c == '\n' || c == '\r'); + let trimmed = + line.content.trim_matches(|c| c == '\n' || c == '\r'); - let filled = if selected { - // selected line - format!("{:w$}\n", trimmed, w = width as usize) - } else { - // weird eof missing eol line - format!("{}\n", trimmed) - }; - //TODO: allow customize tabsize - let content = Cow::from(filled.replace("\t", " ")); + let filled = if selected { + // selected line + format!("{:w$}\n", trimmed, w = width as usize) + } else { + // weird eof missing eol line + format!("{}\n", trimmed) + }; + //TODO: allow customize tabsize + let content = Cow::from(filled.replace("\t", " ")); - Spans::from(vec![ - left_side_of_line, - Span::styled( - content, - theme.diff_line(line.line_type, selected), - ), - ]) - } + Spans::from(vec![ + left_side_of_line, + Span::styled( + content, + theme.diff_line(line.line_type, selected), + ), + ]) + } - const fn hunk_visible( - hunk_min: usize, - hunk_max: usize, - min: usize, - max: usize, - ) -> bool { - // full overlap - if hunk_min <= min && hunk_max >= max { - return true; - } + const fn hunk_visible( + hunk_min: usize, + hunk_max: usize, + min: usize, + max: usize, + ) -> bool { + // full overlap + if hunk_min <= min && hunk_max >= max { + return true; + } - // partly overlap - if (hunk_min >= min && hunk_min <= max) - || (hunk_max >= min && hunk_max <= max) - { - return true; - } + // partly overlap + if (hunk_min >= min && hunk_min <= max) + || (hunk_max >= min && hunk_max <= max) + { + return true; + } - false - } + false + } - fn unstage_hunk(&mut self) -> Result<()> { - if let Some(diff) = &self.diff { - if let Some(hunk) = self.selected_hunk { - let hash = diff.hunks[hunk].header_hash; - sync::unstage_hunk(CWD, &self.current.path, hash)?; - self.queue_update(); - } - } + fn unstage_hunk(&mut self) -> Result<()> { + if let Some(diff) = &self.diff { + if let Some(hunk) = self.selected_hunk { + let hash = diff.hunks[hunk].header_hash; + sync::unstage_hunk(CWD, &self.current.path, hash)?; + self.queue_update(); + } + } - Ok(()) - } + Ok(()) + } - fn stage_hunk(&mut self) -> Result<()> { - if let Some(diff) = &self.diff { - if let Some(hunk) = self.selected_hunk { - if diff.untracked { - sync::stage_add_file( - CWD, - Path::new(&self.current.path), - )?; - } else { - let hash = diff.hunks[hunk].header_hash; - sync::stage_hunk(CWD, &self.current.path, hash)?; - } + fn stage_hunk(&mut self) -> Result<()> { + if let Some(diff) = &self.diff { + if let Some(hunk) = self.selected_hunk { + if diff.untracked { + sync::stage_add_file( + CWD, + Path::new(&self.current.path), + )?; + } else { + let hash = diff.hunks[hunk].header_hash; + sync::stage_hunk(CWD, &self.current.path, hash)?; + } - self.queue_update(); - } - } + self.queue_update(); + } + } - Ok(()) - } + Ok(()) + } - fn queue_update(&self) { - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - } + fn queue_update(&self) { + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + } - fn reset_hunk(&self) { - if let Some(diff) = &self.diff { - if let Some(hunk) = self.selected_hunk { - let hash = diff.hunks[hunk].header_hash; + fn reset_hunk(&self) { + if let Some(diff) = &self.diff { + if let Some(hunk) = self.selected_hunk { + let hash = diff.hunks[hunk].header_hash; - self.queue.push(InternalEvent::ConfirmAction( - Action::ResetHunk( - self.current.path.clone(), - hash, - ), - )); - } - } - } + self.queue.push(InternalEvent::ConfirmAction( + Action::ResetHunk( + self.current.path.clone(), + hash, + ), + )); + } + } + } - fn reset_lines(&self) { - self.queue.push(InternalEvent::ConfirmAction( - Action::ResetLines( - self.current.path.clone(), - self.selected_lines(), - ), - )); - } + fn reset_lines(&self) { + self.queue.push(InternalEvent::ConfirmAction( + Action::ResetLines( + self.current.path.clone(), + self.selected_lines(), + ), + )); + } - fn stage_lines(&self) { - if let Some(diff) = &self.diff { - //TODO: support untracked files aswell - if !diff.untracked { - let selected_lines = self.selected_lines(); + fn stage_lines(&self) { + if let Some(diff) = &self.diff { + //TODO: support untracked files aswell + if !diff.untracked { + let selected_lines = self.selected_lines(); - try_or_popup!( - self, - "(un)stage lines:", - sync::stage_lines( - CWD, - &self.current.path, - self.is_stage(), - &selected_lines, - ) - ); + try_or_popup!( + self, + "(un)stage lines:", + sync::stage_lines( + CWD, + &self.current.path, + self.is_stage(), + &selected_lines, + ) + ); - self.queue_update(); - } - } - } + self.queue_update(); + } + } + } - fn selected_lines(&self) -> Vec { - self.diff - .as_ref() - .map(|diff| { - diff.hunks - .iter() - .flat_map(|hunk| hunk.lines.iter()) - .enumerate() - .filter_map(|(i, line)| { - let is_add_or_delete = line.line_type - == DiffLineType::Add - || line.line_type == DiffLineType::Delete; - if self.selection.contains(i) - && is_add_or_delete - { - Some(line.position) - } else { - None - } - }) - .collect() - }) - .unwrap_or_default() - } + fn selected_lines(&self) -> Vec { + self.diff + .as_ref() + .map(|diff| { + diff.hunks + .iter() + .flat_map(|hunk| hunk.lines.iter()) + .enumerate() + .filter_map(|(i, line)| { + let is_add_or_delete = line.line_type + == DiffLineType::Add + || line.line_type == DiffLineType::Delete; + if self.selection.contains(i) + && is_add_or_delete + { + Some(line.position) + } else { + None + } + }) + .collect() + }) + .unwrap_or_default() + } - fn reset_untracked(&self) { - self.queue.push(InternalEvent::ConfirmAction(Action::Reset( - ResetItem { - path: self.current.path.clone(), - is_folder: false, - }, - ))); - } + fn reset_untracked(&self) { + self.queue.push(InternalEvent::ConfirmAction(Action::Reset( + ResetItem { + path: self.current.path.clone(), + is_folder: false, + }, + ))); + } - fn stage_unstage_hunk(&mut self) -> Result<()> { - if self.current.is_stage { - self.unstage_hunk()?; - } else { - self.stage_hunk()?; - } + fn stage_unstage_hunk(&mut self) -> Result<()> { + if self.current.is_stage { + self.unstage_hunk()?; + } else { + self.stage_hunk()?; + } - Ok(()) - } + Ok(()) + } - const fn is_stage(&self) -> bool { - self.current.is_stage - } + const fn is_stage(&self) -> bool { + self.current.is_stage + } } impl DrawableComponent for DiffComponent { - fn draw( - &self, - f: &mut Frame, - r: Rect, - ) -> Result<()> { - self.current_size.set(( - r.width.saturating_sub(2), - r.height.saturating_sub(2), - )); + fn draw( + &self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { + self.current_size.set(( + r.width.saturating_sub(2), + r.height.saturating_sub(2), + )); - let current_height = self.current_size.get().1; + let current_height = self.current_size.get().1; - self.scroll.update( - self.selection.get_end(), - self.lines_count(), - usize::from(current_height), - ); + self.scroll.update( + self.selection.get_end(), + self.lines_count(), + usize::from(current_height), + ); - let title = format!( - "{}{}", - strings::title_diff(&self.key_config), - self.current.path - ); + let title = format!( + "{}{}", + strings::title_diff(&self.key_config), + self.current.path + ); - let txt = if self.pending { - vec![Spans::from(vec![Span::styled( - Cow::from(strings::loading_text(&self.key_config)), - self.theme.text(false, false), - )])] - } else { - self.get_text(r.width, current_height) - }; + let txt = if self.pending { + vec![Spans::from(vec![Span::styled( + Cow::from(strings::loading_text(&self.key_config)), + self.theme.text(false, false), + )])] + } else { + self.get_text(r.width, current_height) + }; - f.render_widget( - Paragraph::new(txt).block( - Block::default() - .title(Span::styled( - title.as_str(), - self.theme.title(self.focused), - )) - .borders(Borders::ALL) - .border_style(self.theme.block(self.focused)), - ), - r, - ); + f.render_widget( + Paragraph::new(txt).block( + Block::default() + .title(Span::styled( + title.as_str(), + self.theme.title(self.focused), + )) + .borders(Borders::ALL) + .border_style(self.theme.block(self.focused)), + ), + r, + ); - if self.focused { - self.scroll.draw(f, r, &self.theme); - } + if self.focused { + self.scroll.draw(f, r, &self.theme); + } - Ok(()) - } + Ok(()) + } } impl Component for DiffComponent { - fn commands( - &self, - out: &mut Vec, - _force_all: bool, - ) -> CommandBlocking { - out.push(CommandInfo::new( - strings::commands::scroll(&self.key_config), - self.can_scroll(), - self.focused, - )); + fn commands( + &self, + out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + out.push(CommandInfo::new( + strings::commands::scroll(&self.key_config), + self.can_scroll(), + self.focused, + )); - out.push( - CommandInfo::new( - strings::commands::diff_home_end(&self.key_config), - self.can_scroll(), - self.focused, - ) - .hidden(), - ); + out.push( + CommandInfo::new( + strings::commands::diff_home_end(&self.key_config), + self.can_scroll(), + self.focused, + ) + .hidden(), + ); - if !self.is_immutable { - out.push(CommandInfo::new( - strings::commands::diff_hunk_remove(&self.key_config), - self.selected_hunk.is_some(), - self.focused && self.is_stage(), - )); - out.push(CommandInfo::new( - strings::commands::diff_hunk_add(&self.key_config), - self.selected_hunk.is_some(), - self.focused && !self.is_stage(), - )); - out.push(CommandInfo::new( - strings::commands::diff_hunk_revert(&self.key_config), - self.selected_hunk.is_some(), - self.focused && !self.is_stage(), - )); - out.push(CommandInfo::new( - strings::commands::diff_lines_revert( - &self.key_config, - ), - //TODO: only if any modifications are selected - true, - self.focused && !self.is_stage(), - )); - out.push(CommandInfo::new( - strings::commands::diff_lines_stage(&self.key_config), - //TODO: only if any modifications are selected - true, - self.focused && !self.is_stage(), - )); - out.push(CommandInfo::new( - strings::commands::diff_lines_unstage( - &self.key_config, - ), - //TODO: only if any modifications are selected - true, - self.focused && self.is_stage(), - )); - } + if !self.is_immutable { + out.push(CommandInfo::new( + strings::commands::diff_hunk_remove(&self.key_config), + self.selected_hunk.is_some(), + self.focused && self.is_stage(), + )); + out.push(CommandInfo::new( + strings::commands::diff_hunk_add(&self.key_config), + self.selected_hunk.is_some(), + self.focused && !self.is_stage(), + )); + out.push(CommandInfo::new( + strings::commands::diff_hunk_revert(&self.key_config), + self.selected_hunk.is_some(), + self.focused && !self.is_stage(), + )); + out.push(CommandInfo::new( + strings::commands::diff_lines_revert( + &self.key_config, + ), + //TODO: only if any modifications are selected + true, + self.focused && !self.is_stage(), + )); + out.push(CommandInfo::new( + strings::commands::diff_lines_stage(&self.key_config), + //TODO: only if any modifications are selected + true, + self.focused && !self.is_stage(), + )); + out.push(CommandInfo::new( + strings::commands::diff_lines_unstage( + &self.key_config, + ), + //TODO: only if any modifications are selected + true, + self.focused && self.is_stage(), + )); + } - out.push(CommandInfo::new( - strings::commands::copy(&self.key_config), - true, - self.focused, - )); + out.push(CommandInfo::new( + strings::commands::copy(&self.key_config), + true, + self.focused, + )); - CommandBlocking::PassingOn - } + CommandBlocking::PassingOn + } - #[allow(clippy::cognitive_complexity)] - fn event(&mut self, ev: Event) -> Result { - if self.focused { - if let Event::Key(e) = ev { - return if e == self.key_config.move_down { - self.move_selection(ScrollType::Down); - Ok(EventState::Consumed) - } else if e == self.key_config.shift_down { - self.modify_selection(Direction::Down); - Ok(EventState::Consumed) - } else if e == self.key_config.shift_up { - self.modify_selection(Direction::Up); - Ok(EventState::Consumed) - } else if e == self.key_config.end { - self.move_selection(ScrollType::End); - Ok(EventState::Consumed) - } else if e == self.key_config.home { - self.move_selection(ScrollType::Home); - Ok(EventState::Consumed) - } else if e == self.key_config.move_up { - self.move_selection(ScrollType::Up); - Ok(EventState::Consumed) - } else if e == self.key_config.page_up { - self.move_selection(ScrollType::PageUp); - Ok(EventState::Consumed) - } else if e == self.key_config.page_down { - self.move_selection(ScrollType::PageDown); - Ok(EventState::Consumed) - } else if e == self.key_config.enter - && !self.is_immutable - { - try_or_popup!( - self, - "hunk error:", - self.stage_unstage_hunk() - ); + #[allow(clippy::cognitive_complexity)] + fn event(&mut self, ev: Event) -> Result { + if self.focused { + if let Event::Key(e) = ev { + return if e == self.key_config.move_down { + self.move_selection(ScrollType::Down); + Ok(EventState::Consumed) + } else if e == self.key_config.shift_down { + self.modify_selection(Direction::Down); + Ok(EventState::Consumed) + } else if e == self.key_config.shift_up { + self.modify_selection(Direction::Up); + Ok(EventState::Consumed) + } else if e == self.key_config.end { + self.move_selection(ScrollType::End); + Ok(EventState::Consumed) + } else if e == self.key_config.home { + self.move_selection(ScrollType::Home); + Ok(EventState::Consumed) + } else if e == self.key_config.move_up { + self.move_selection(ScrollType::Up); + Ok(EventState::Consumed) + } else if e == self.key_config.page_up { + self.move_selection(ScrollType::PageUp); + Ok(EventState::Consumed) + } else if e == self.key_config.page_down { + self.move_selection(ScrollType::PageDown); + Ok(EventState::Consumed) + } else if e == self.key_config.enter + && !self.is_immutable + { + try_or_popup!( + self, + "hunk error:", + self.stage_unstage_hunk() + ); - Ok(EventState::Consumed) - } else if e == self.key_config.status_reset_item - && !self.is_immutable - && !self.is_stage() - { - if let Some(diff) = &self.diff { - if diff.untracked { - self.reset_untracked(); - } else { - self.reset_hunk(); - } - } - Ok(EventState::Consumed) - } else if e == self.key_config.diff_stage_lines - && !self.is_immutable - { - self.stage_lines(); - Ok(EventState::Consumed) - } else if e == self.key_config.diff_reset_lines - && !self.is_immutable - && !self.is_stage() - { - if let Some(diff) = &self.diff { - //TODO: reset untracked lines - if !diff.untracked { - self.reset_lines(); - } - } - Ok(EventState::Consumed) - } else if e == self.key_config.copy { - self.copy_selection(); - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - }; - } - } + Ok(EventState::Consumed) + } else if e == self.key_config.status_reset_item + && !self.is_immutable + && !self.is_stage() + { + if let Some(diff) = &self.diff { + if diff.untracked { + self.reset_untracked(); + } else { + self.reset_hunk(); + } + } + Ok(EventState::Consumed) + } else if e == self.key_config.diff_stage_lines + && !self.is_immutable + { + self.stage_lines(); + Ok(EventState::Consumed) + } else if e == self.key_config.diff_reset_lines + && !self.is_immutable + && !self.is_stage() + { + if let Some(diff) = &self.diff { + //TODO: reset untracked lines + if !diff.untracked { + self.reset_lines(); + } + } + Ok(EventState::Consumed) + } else if e == self.key_config.copy { + self.copy_selection(); + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + }; + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn focused(&self) -> bool { - self.focused - } - fn focus(&mut self, focus: bool) { - self.focused = focus; - } + fn focused(&self) -> bool { + self.focused + } + fn focus(&mut self, focus: bool) { + self.focused = focus; + } } diff --git a/src/components/externaleditor.rs b/src/components/externaleditor.rs index 94d3189f..e504690f 100644 --- a/src/components/externaleditor.rs +++ b/src/components/externaleditor.rs @@ -1,190 +1,190 @@ use crate::{ - components::{ - visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - strings, - ui::{self, style::SharedTheme}, + components::{ + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + strings, + ui::{self, style::SharedTheme}, }; use anyhow::{anyhow, bail, Result}; use asyncgit::{ - sync::{get_config_string, utils::repo_work_dir}, - CWD, + sync::{get_config_string, utils::repo_work_dir}, + CWD, }; use crossterm::{ - event::Event, - terminal::{EnterAlternateScreen, LeaveAlternateScreen}, - ExecutableCommand, + event::Event, + terminal::{EnterAlternateScreen, LeaveAlternateScreen}, + ExecutableCommand, }; use scopeguard::defer; use std::ffi::OsStr; use std::{env, io, path::Path, process::Command}; use tui::{ - backend::Backend, - layout::Rect, - text::{Span, Spans}, - widgets::{Block, BorderType, Borders, Clear, Paragraph}, - Frame, + backend::Backend, + layout::Rect, + text::{Span, Spans}, + widgets::{Block, BorderType, Borders, Clear, Paragraph}, + Frame, }; /// pub struct ExternalEditorComponent { - visible: bool, - theme: SharedTheme, - key_config: SharedKeyConfig, + visible: bool, + theme: SharedTheme, + key_config: SharedKeyConfig, } impl ExternalEditorComponent { - /// - pub fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - visible: false, - theme, - key_config, - } - } + /// + pub fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + visible: false, + theme, + key_config, + } + } - /// opens file at given `path` in an available editor - pub fn open_file_in_editor(path: &Path) -> Result<()> { - let work_dir = repo_work_dir(CWD)?; + /// opens file at given `path` in an available editor + pub fn open_file_in_editor(path: &Path) -> Result<()> { + let work_dir = repo_work_dir(CWD)?; - let path = if path.is_relative() { - Path::new(&work_dir).join(path) - } else { - path.into() - }; + let path = if path.is_relative() { + Path::new(&work_dir).join(path) + } else { + path.into() + }; - if !path.exists() { - bail!("file not found: {:?}", path); - } + if !path.exists() { + bail!("file not found: {:?}", path); + } - io::stdout().execute(LeaveAlternateScreen)?; - defer! { - io::stdout().execute(EnterAlternateScreen).expect("reset terminal"); - } + io::stdout().execute(LeaveAlternateScreen)?; + defer! { + io::stdout().execute(EnterAlternateScreen).expect("reset terminal"); + } - let environment_options = ["GIT_EDITOR", "VISUAL", "EDITOR"]; + let environment_options = ["GIT_EDITOR", "VISUAL", "EDITOR"]; - let editor = env::var(environment_options[0]) - .ok() - .or_else(|| get_config_string(CWD, "core.editor").ok()?) - .or_else(|| env::var(environment_options[1]).ok()) - .or_else(|| env::var(environment_options[2]).ok()) - .unwrap_or_else(|| String::from("vi")); + let editor = env::var(environment_options[0]) + .ok() + .or_else(|| get_config_string(CWD, "core.editor").ok()?) + .or_else(|| env::var(environment_options[1]).ok()) + .or_else(|| env::var(environment_options[2]).ok()) + .unwrap_or_else(|| String::from("vi")); - // TODO: proper handling arguments containing whitespaces - // This does not do the right thing if the input is `editor --something "with spaces"` + // TODO: proper handling arguments containing whitespaces + // This does not do the right thing if the input is `editor --something "with spaces"` - // deal with "editor name with spaces" p1 p2 p3 - // and with "editor_no_spaces" p1 p2 p3 - // does not address spaces in pn - let mut echars = editor.chars().peekable(); + // deal with "editor name with spaces" p1 p2 p3 + // and with "editor_no_spaces" p1 p2 p3 + // does not address spaces in pn + let mut echars = editor.chars().peekable(); - let first_char = *echars.peek().ok_or_else(|| { - anyhow!( - "editor env variable found empty: {}", - environment_options.join(" or ") - ) - })?; - let command: String = if first_char == '\"' { - echars - .by_ref() - .skip(1) - .take_while(|c| *c != '\"') - .collect() - } else { - echars.by_ref().take_while(|c| *c != ' ').collect() - }; + let first_char = *echars.peek().ok_or_else(|| { + anyhow!( + "editor env variable found empty: {}", + environment_options.join(" or ") + ) + })?; + let command: String = if first_char == '\"' { + echars + .by_ref() + .skip(1) + .take_while(|c| *c != '\"') + .collect() + } else { + echars.by_ref().take_while(|c| *c != ' ').collect() + }; - let remainder_str = echars.collect::(); - let remainder = remainder_str.split_whitespace(); + let remainder_str = echars.collect::(); + let remainder = remainder_str.split_whitespace(); - let mut args: Vec<&OsStr> = - remainder.map(|s| OsStr::new(s)).collect(); + let mut args: Vec<&OsStr> = + remainder.map(|s| OsStr::new(s)).collect(); - args.push(path.as_os_str()); + args.push(path.as_os_str()); - Command::new(command.clone()) - .current_dir(work_dir) - .args(args) - .status() - .map_err(|e| anyhow!("\"{}\": {}", command, e))?; + Command::new(command.clone()) + .current_dir(work_dir) + .args(args) + .status() + .map_err(|e| anyhow!("\"{}\": {}", command, e))?; - Ok(()) - } + Ok(()) + } } impl DrawableComponent for ExternalEditorComponent { - fn draw( - &self, - f: &mut Frame, - _rect: Rect, - ) -> Result<()> { - if self.visible { - let txt = Spans::from( - strings::msg_opening_editor(&self.key_config) - .split('\n') - .map(|string| { - Span::raw::(string.to_string()) - }) - .collect::>(), - ); + fn draw( + &self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { + if self.visible { + let txt = Spans::from( + strings::msg_opening_editor(&self.key_config) + .split('\n') + .map(|string| { + Span::raw::(string.to_string()) + }) + .collect::>(), + ); - let area = ui::centered_rect_absolute(25, 3, f.size()); - f.render_widget(Clear, area); - f.render_widget( - Paragraph::new(txt) - .block( - Block::default() - .borders(Borders::ALL) - .border_type(BorderType::Thick) - .border_style(self.theme.block(true)), - ) - .style(self.theme.block(true)), - area, - ); - } + let area = ui::centered_rect_absolute(25, 3, f.size()); + f.render_widget(Clear, area); + f.render_widget( + Paragraph::new(txt) + .block( + Block::default() + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .border_style(self.theme.block(true)), + ) + .style(self.theme.block(true)), + area, + ); + } - Ok(()) - } + Ok(()) + } } impl Component for ExternalEditorComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible && !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible && !force_all { + out.clear(); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, _ev: Event) -> Result { - if self.visible { - return Ok(EventState::Consumed); - } + fn event(&mut self, _ev: Event) -> Result { + if self.visible { + return Ok(EventState::Consumed); + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/filetree.rs b/src/components/filetree.rs index ba2a2f9c..e990fe9c 100644 --- a/src/components/filetree.rs +++ b/src/components/filetree.rs @@ -1,17 +1,17 @@ use super::{ - utils::{ - filetree::{FileTreeItem, FileTreeItemKind}, - statustree::{MoveSelection, StatusTree}, - }, - CommandBlocking, DrawableComponent, + utils::{ + filetree::{FileTreeItem, FileTreeItemKind}, + statustree::{MoveSelection, StatusTree}, + }, + CommandBlocking, DrawableComponent, }; use crate::{ - components::{CommandInfo, Component, EventState}, - keys::SharedKeyConfig, - queue::{InternalEvent, NeedsUpdate, Queue}, - strings::{self, order}, - ui, - ui::style::SharedTheme, + components::{CommandInfo, Component, EventState}, + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings::{self, order}, + ui, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{hash, StatusItem, StatusItemType}; @@ -24,535 +24,535 @@ use tui::{backend::Backend, layout::Rect, text::Span, Frame}; /// pub struct FileTreeComponent { - title: String, - tree: StatusTree, - pending: bool, - current_hash: u64, - focused: bool, - show_selection: bool, - queue: Option, - theme: SharedTheme, - key_config: SharedKeyConfig, - scroll_top: Cell, + title: String, + tree: StatusTree, + pending: bool, + current_hash: u64, + focused: bool, + show_selection: bool, + queue: Option, + theme: SharedTheme, + key_config: SharedKeyConfig, + scroll_top: Cell, } impl FileTreeComponent { - /// - pub fn new( - title: &str, - focus: bool, - queue: Option, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - title: title.to_string(), - tree: StatusTree::default(), - current_hash: 0, - focused: focus, - show_selection: focus, - queue, - theme, - key_config, - scroll_top: Cell::new(0), - pending: true, - } - } + /// + pub fn new( + title: &str, + focus: bool, + queue: Option, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + title: title.to_string(), + tree: StatusTree::default(), + current_hash: 0, + focused: focus, + show_selection: focus, + queue, + theme, + key_config, + scroll_top: Cell::new(0), + pending: true, + } + } - /// - pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { - self.pending = false; - let new_hash = hash(list); - if self.current_hash != new_hash { - self.tree.update(list)?; - self.current_hash = new_hash; - } + /// + pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { + self.pending = false; + let new_hash = hash(list); + if self.current_hash != new_hash { + self.tree.update(list)?; + self.current_hash = new_hash; + } - Ok(()) - } + Ok(()) + } - /// - pub fn selection(&self) -> Option { - self.tree.selected_item() - } + /// + pub fn selection(&self) -> Option { + self.tree.selected_item() + } - /// - pub fn selection_file(&self) -> Option { - self.tree.selected_item().and_then(|f| { - if let FileTreeItemKind::File(f) = f.kind { - Some(f) - } else { - None - } - }) - } + /// + pub fn selection_file(&self) -> Option { + self.tree.selected_item().and_then(|f| { + if let FileTreeItemKind::File(f) = f.kind { + Some(f) + } else { + None + } + }) + } - /// - pub fn show_selection(&mut self, show: bool) { - self.show_selection = show; - } + /// + pub fn show_selection(&mut self, show: bool) { + self.show_selection = show; + } - /// returns true if list is empty - pub fn is_empty(&self) -> bool { - self.tree.is_empty() - } + /// returns true if list is empty + pub fn is_empty(&self) -> bool { + self.tree.is_empty() + } - /// - pub const fn file_count(&self) -> usize { - self.tree.tree.file_count() - } + /// + pub const fn file_count(&self) -> usize { + self.tree.tree.file_count() + } - /// - pub fn set_title(&mut self, title: String) { - self.title = title; - } + /// + pub fn set_title(&mut self, title: String) { + self.title = title; + } - /// - pub fn clear(&mut self) -> Result<()> { - self.current_hash = 0; - self.pending = true; - self.tree.update(&[]) - } + /// + pub fn clear(&mut self) -> Result<()> { + self.current_hash = 0; + self.pending = true; + self.tree.update(&[]) + } - /// - pub fn is_file_seleted(&self) -> bool { - self.tree.selected_item().map_or(false, |item| { - match item.kind { - FileTreeItemKind::File(_) => true, - FileTreeItemKind::Path(..) => false, - } - }) - } + /// + pub fn is_file_seleted(&self) -> bool { + self.tree.selected_item().map_or(false, |item| { + match item.kind { + FileTreeItemKind::File(_) => true, + FileTreeItemKind::Path(..) => false, + } + }) + } - fn move_selection(&mut self, dir: MoveSelection) -> bool { - let changed = self.tree.move_selection(dir); + fn move_selection(&mut self, dir: MoveSelection) -> bool { + let changed = self.tree.move_selection(dir); - if changed { - if let Some(ref queue) = self.queue { - queue.push(InternalEvent::Update(NeedsUpdate::DIFF)); - } - } + if changed { + if let Some(ref queue) = self.queue { + queue.push(InternalEvent::Update(NeedsUpdate::DIFF)); + } + } - changed - } + changed + } - const fn item_status_char(item_type: StatusItemType) -> char { - match item_type { - StatusItemType::Modified => 'M', - StatusItemType::New => '+', - StatusItemType::Deleted => '-', - StatusItemType::Renamed => 'R', - StatusItemType::Typechange => ' ', - StatusItemType::Conflicted => '!', - } - } + const fn item_status_char(item_type: StatusItemType) -> char { + match item_type { + StatusItemType::Modified => 'M', + StatusItemType::New => '+', + StatusItemType::Deleted => '-', + StatusItemType::Renamed => 'R', + StatusItemType::Typechange => ' ', + StatusItemType::Conflicted => '!', + } + } - fn item_to_text<'b>( - string: &str, - indent: usize, - visible: bool, - file_item_kind: &FileTreeItemKind, - width: u16, - selected: bool, - theme: &'b SharedTheme, - ) -> Option> { - let indent_str = if indent == 0 { - String::from("") - } else { - format!("{:w$}", " ", w = (indent as usize) * 2) - }; + fn item_to_text<'b>( + string: &str, + indent: usize, + visible: bool, + file_item_kind: &FileTreeItemKind, + width: u16, + selected: bool, + theme: &'b SharedTheme, + ) -> Option> { + let indent_str = if indent == 0 { + String::from("") + } else { + format!("{:w$}", " ", w = (indent as usize) * 2) + }; - if !visible { - return None; - } + if !visible { + return None; + } - match file_item_kind { - FileTreeItemKind::File(status_item) => { - let status_char = - Self::item_status_char(status_item.status); - let file = Path::new(&status_item.path) - .file_name() - .and_then(std::ffi::OsStr::to_str) - .expect("invalid path."); + match file_item_kind { + FileTreeItemKind::File(status_item) => { + let status_char = + Self::item_status_char(status_item.status); + let file = Path::new(&status_item.path) + .file_name() + .and_then(std::ffi::OsStr::to_str) + .expect("invalid path."); - let txt = if selected { - format!( - "{} {}{:w$}", - status_char, - indent_str, - file, - w = width as usize - ) - } else { - format!("{} {}{}", status_char, indent_str, file) - }; + let txt = if selected { + format!( + "{} {}{:w$}", + status_char, + indent_str, + file, + w = width as usize + ) + } else { + format!("{} {}{}", status_char, indent_str, file) + }; - Some(Span::styled( - Cow::from(txt), - theme.item(status_item.status, selected), - )) - } + Some(Span::styled( + Cow::from(txt), + theme.item(status_item.status, selected), + )) + } - FileTreeItemKind::Path(path_collapsed) => { - let collapse_char = - if path_collapsed.0 { '▸' } else { '▾' }; + FileTreeItemKind::Path(path_collapsed) => { + let collapse_char = + if path_collapsed.0 { '▸' } else { '▾' }; - let txt = if selected { - format!( - " {}{}{:w$}", - indent_str, - collapse_char, - string, - w = width as usize - ) - } else { - format!( - " {}{}{}", - indent_str, collapse_char, string, - ) - }; + let txt = if selected { + format!( + " {}{}{:w$}", + indent_str, + collapse_char, + string, + w = width as usize + ) + } else { + format!( + " {}{}{}", + indent_str, collapse_char, string, + ) + }; - Some(Span::styled( - Cow::from(txt), - theme.text(true, selected), - )) - } - } - } + Some(Span::styled( + Cow::from(txt), + theme.text(true, selected), + )) + } + } + } - /// Returns a Vec which is used to draw the `FileTreeComponent` correctly, - /// allowing folders to be folded up if they are alone in their directory - fn build_vec_text_draw_info_for_drawing( - &self, - ) -> (Vec, usize, usize) { - let mut should_skip_over: usize = 0; - let mut selection_offset: usize = 0; - let mut selection_offset_visible: usize = 0; - let mut vec_draw_text_info: Vec = vec![]; - let tree_items = self.tree.tree.items(); + /// Returns a Vec which is used to draw the `FileTreeComponent` correctly, + /// allowing folders to be folded up if they are alone in their directory + fn build_vec_text_draw_info_for_drawing( + &self, + ) -> (Vec, usize, usize) { + let mut should_skip_over: usize = 0; + let mut selection_offset: usize = 0; + let mut selection_offset_visible: usize = 0; + let mut vec_draw_text_info: Vec = vec![]; + let tree_items = self.tree.tree.items(); - for (index, item) in tree_items.iter().enumerate() { - if should_skip_over > 0 { - should_skip_over -= 1; - continue; - } + for (index, item) in tree_items.iter().enumerate() { + if should_skip_over > 0 { + should_skip_over -= 1; + continue; + } - let index_above_select = - index < self.tree.selection.unwrap_or(0); + let index_above_select = + index < self.tree.selection.unwrap_or(0); - if !item.info.visible && index_above_select { - selection_offset_visible += 1; - } + if !item.info.visible && index_above_select { + selection_offset_visible += 1; + } - vec_draw_text_info.push(TextDrawInfo { - name: item.info.path.clone(), - indent: item.info.indent, - visible: item.info.visible, - item_kind: &item.kind, - }); + vec_draw_text_info.push(TextDrawInfo { + name: item.info.path.clone(), + indent: item.info.indent, + visible: item.info.visible, + item_kind: &item.kind, + }); - let mut idx_temp = index; + let mut idx_temp = index; - while idx_temp < tree_items.len().saturating_sub(2) - && tree_items[idx_temp].info.indent - < tree_items[idx_temp + 1].info.indent - { - // fold up the folder/file - idx_temp += 1; - should_skip_over += 1; + while idx_temp < tree_items.len().saturating_sub(2) + && tree_items[idx_temp].info.indent + < tree_items[idx_temp + 1].info.indent + { + // fold up the folder/file + idx_temp += 1; + should_skip_over += 1; - // don't fold files up - //TODO: remove once fixed (https://github.com/rust-lang/rust-clippy/issues/7383) - #[allow(clippy::if_same_then_else)] - if let FileTreeItemKind::File(_) = - &tree_items[idx_temp].kind - { - should_skip_over -= 1; - break; - } - // don't fold up if more than one folder in folder - else if self - .tree - .tree - .multiple_items_at_path(idx_temp) - { - should_skip_over -= 1; - break; - } + // don't fold files up + //TODO: remove once fixed (https://github.com/rust-lang/rust-clippy/issues/7383) + #[allow(clippy::if_same_then_else)] + if let FileTreeItemKind::File(_) = + &tree_items[idx_temp].kind + { + should_skip_over -= 1; + break; + } + // don't fold up if more than one folder in folder + else if self + .tree + .tree + .multiple_items_at_path(idx_temp) + { + should_skip_over -= 1; + break; + } - // There is only one item at this level (i.e only one folder in the folder), - // so do fold up + // There is only one item at this level (i.e only one folder in the folder), + // so do fold up - let vec_draw_text_info_len = vec_draw_text_info.len(); - vec_draw_text_info[vec_draw_text_info_len - 1] - .name += &(String::from("/") - + &tree_items[idx_temp].info.path); - if index_above_select { - selection_offset += 1; - } - } - } - ( - vec_draw_text_info, - selection_offset, - selection_offset_visible, - ) - } + let vec_draw_text_info_len = vec_draw_text_info.len(); + vec_draw_text_info[vec_draw_text_info_len - 1] + .name += &(String::from("/") + + &tree_items[idx_temp].info.path); + if index_above_select { + selection_offset += 1; + } + } + } + ( + vec_draw_text_info, + selection_offset, + selection_offset_visible, + ) + } } /// Used for drawing the `FileTreeComponent` struct TextDrawInfo<'a> { - name: String, - indent: u8, - visible: bool, - item_kind: &'a FileTreeItemKind, + name: String, + indent: u8, + visible: bool, + item_kind: &'a FileTreeItemKind, } impl DrawableComponent for FileTreeComponent { - fn draw( - &self, - f: &mut Frame, - r: Rect, - ) -> Result<()> { - if self.pending { - let items = vec![Span::styled( - Cow::from(strings::loading_text(&self.key_config)), - self.theme.text(false, false), - )]; + fn draw( + &self, + f: &mut Frame, + r: Rect, + ) -> Result<()> { + if self.pending { + let items = vec![Span::styled( + Cow::from(strings::loading_text(&self.key_config)), + self.theme.text(false, false), + )]; - ui::draw_list( - f, - r, - self.title.as_str(), - items.into_iter(), - self.focused, - &self.theme, - ); - } else { - let ( - vec_draw_text_info, - selection_offset, - selection_offset_visible, - ) = self.build_vec_text_draw_info_for_drawing(); + ui::draw_list( + f, + r, + self.title.as_str(), + items.into_iter(), + self.focused, + &self.theme, + ); + } else { + let ( + vec_draw_text_info, + selection_offset, + selection_offset_visible, + ) = self.build_vec_text_draw_info_for_drawing(); - let select = self - .tree - .selection - .map(|idx| idx.saturating_sub(selection_offset)) - .unwrap_or_default(); - let tree_height = r.height.saturating_sub(2) as usize; + let select = self + .tree + .selection + .map(|idx| idx.saturating_sub(selection_offset)) + .unwrap_or_default(); + let tree_height = r.height.saturating_sub(2) as usize; - self.scroll_top.set(ui::calc_scroll_top( - self.scroll_top.get(), - tree_height, - select.saturating_sub(selection_offset_visible), - )); + self.scroll_top.set(ui::calc_scroll_top( + self.scroll_top.get(), + tree_height, + select.saturating_sub(selection_offset_visible), + )); - let items = vec_draw_text_info - .iter() - .enumerate() - .filter_map(|(index, draw_text_info)| { - Self::item_to_text( - &draw_text_info.name, - draw_text_info.indent as usize, - draw_text_info.visible, - draw_text_info.item_kind, - r.width, - self.show_selection && select == index, - &self.theme, - ) - }) - .skip(self.scroll_top.get()); - ui::draw_list( - f, - r, - self.title.as_str(), - items, - self.focused, - &self.theme, - ); - } + let items = vec_draw_text_info + .iter() + .enumerate() + .filter_map(|(index, draw_text_info)| { + Self::item_to_text( + &draw_text_info.name, + draw_text_info.indent as usize, + draw_text_info.visible, + draw_text_info.item_kind, + r.width, + self.show_selection && select == index, + &self.theme, + ) + }) + .skip(self.scroll_top.get()); + ui::draw_list( + f, + r, + self.title.as_str(), + items, + self.focused, + &self.theme, + ); + } - Ok(()) - } + Ok(()) + } } impl Component for FileTreeComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - out.push( - CommandInfo::new( - strings::commands::navigate_tree(&self.key_config), - !self.is_empty(), - self.focused || force_all, - ) - .order(order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::blame_file(&self.key_config), - self.selection_file().is_some(), - self.focused || force_all, - ) - .order(order::RARE_ACTION), - ); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + out.push( + CommandInfo::new( + strings::commands::navigate_tree(&self.key_config), + !self.is_empty(), + self.focused || force_all, + ) + .order(order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::blame_file(&self.key_config), + self.selection_file().is_some(), + self.focused || force_all, + ) + .order(order::RARE_ACTION), + ); - CommandBlocking::PassingOn - } + CommandBlocking::PassingOn + } - fn event(&mut self, ev: Event) -> Result { - if self.focused { - if let Event::Key(e) = ev { - return if e == self.key_config.blame { - match (&self.queue, self.selection_file()) { - (Some(queue), Some(status_item)) => { - queue.push(InternalEvent::BlameFile( - status_item.path, - )); + fn event(&mut self, ev: Event) -> Result { + if self.focused { + if let Event::Key(e) = ev { + return if e == self.key_config.blame { + match (&self.queue, self.selection_file()) { + (Some(queue), Some(status_item)) => { + queue.push(InternalEvent::BlameFile( + status_item.path, + )); - Ok(EventState::Consumed) - } - _ => Ok(EventState::NotConsumed), - } - } else if e == self.key_config.move_down { - Ok(self - .move_selection(MoveSelection::Down) - .into()) - } else if e == self.key_config.move_up { - Ok(self.move_selection(MoveSelection::Up).into()) - } else if e == self.key_config.home - || e == self.key_config.shift_up - { - Ok(self - .move_selection(MoveSelection::Home) - .into()) - } else if e == self.key_config.end - || e == self.key_config.shift_down - { - Ok(self.move_selection(MoveSelection::End).into()) - } else if e == self.key_config.move_left { - Ok(self - .move_selection(MoveSelection::Left) - .into()) - } else if e == self.key_config.move_right { - Ok(self - .move_selection(MoveSelection::Right) - .into()) - } else { - Ok(EventState::NotConsumed) - }; - } - } + Ok(EventState::Consumed) + } + _ => Ok(EventState::NotConsumed), + } + } else if e == self.key_config.move_down { + Ok(self + .move_selection(MoveSelection::Down) + .into()) + } else if e == self.key_config.move_up { + Ok(self.move_selection(MoveSelection::Up).into()) + } else if e == self.key_config.home + || e == self.key_config.shift_up + { + Ok(self + .move_selection(MoveSelection::Home) + .into()) + } else if e == self.key_config.end + || e == self.key_config.shift_down + { + Ok(self.move_selection(MoveSelection::End).into()) + } else if e == self.key_config.move_left { + Ok(self + .move_selection(MoveSelection::Left) + .into()) + } else if e == self.key_config.move_right { + Ok(self + .move_selection(MoveSelection::Right) + .into()) + } else { + Ok(EventState::NotConsumed) + }; + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn focused(&self) -> bool { - self.focused - } - fn focus(&mut self, focus: bool) { - self.focused = focus; - self.show_selection(focus); - } + fn focused(&self) -> bool { + self.focused + } + fn focus(&mut self, focus: bool) { + self.focused = focus; + self.show_selection(focus); + } } #[cfg(test)] mod tests { - use super::*; - use asyncgit::StatusItemType; + use super::*; + use asyncgit::StatusItemType; - fn string_vec_to_status(items: &[&str]) -> Vec { - items - .iter() - .map(|a| StatusItem { - path: String::from(*a), - status: StatusItemType::Modified, - }) - .collect::>() - } + fn string_vec_to_status(items: &[&str]) -> Vec { + items + .iter() + .map(|a| StatusItem { + path: String::from(*a), + status: StatusItemType::Modified, + }) + .collect::>() + } - #[test] - fn test_correct_scroll_position() { - let items = string_vec_to_status(&[ - "a/b/b1", // - "a/b/b2", // - "a/c/c1", // - ]); + #[test] + fn test_correct_scroll_position() { + let items = string_vec_to_status(&[ + "a/b/b1", // + "a/b/b2", // + "a/c/c1", // + ]); - //0 a/ - //1 b/ - //2 b1 - //3 b2 - //4 c/ - //5 c1 + //0 a/ + //1 b/ + //2 b1 + //3 b2 + //4 c/ + //5 c1 - // Set up test terminal - let test_backend = tui::backend::TestBackend::new(100, 100); - let mut terminal = tui::Terminal::new(test_backend) - .expect("Unable to set up terminal"); - let mut frame = terminal.get_frame(); + // Set up test terminal + let test_backend = tui::backend::TestBackend::new(100, 100); + let mut terminal = tui::Terminal::new(test_backend) + .expect("Unable to set up terminal"); + let mut frame = terminal.get_frame(); - // set up file tree - let mut ftc = FileTreeComponent::new( - "title", - true, - None, - SharedTheme::default(), - SharedKeyConfig::default(), - ); - ftc.update(&items) - .expect("Updating FileTreeComponent failed"); + // set up file tree + let mut ftc = FileTreeComponent::new( + "title", + true, + None, + SharedTheme::default(), + SharedKeyConfig::default(), + ); + ftc.update(&items) + .expect("Updating FileTreeComponent failed"); - ftc.move_selection(MoveSelection::Down); // Move to b/ - ftc.move_selection(MoveSelection::Left); // Fold b/ - ftc.move_selection(MoveSelection::Down); // Move to c/ + ftc.move_selection(MoveSelection::Down); // Move to b/ + ftc.move_selection(MoveSelection::Left); // Fold b/ + ftc.move_selection(MoveSelection::Down); // Move to c/ - ftc.draw(&mut frame, Rect::new(0, 0, 10, 5)) - .expect("Draw failed"); + ftc.draw(&mut frame, Rect::new(0, 0, 10, 5)) + .expect("Draw failed"); - assert_eq!(ftc.scroll_top.get(), 0); // should still be at top - } + assert_eq!(ftc.scroll_top.get(), 0); // should still be at top + } - #[test] - fn test_correct_foldup_and_not_visible_scroll_position() { - let items = string_vec_to_status(&[ - "a/b/b1", // - "c/d1", // - "c/d2", // - ]); + #[test] + fn test_correct_foldup_and_not_visible_scroll_position() { + let items = string_vec_to_status(&[ + "a/b/b1", // + "c/d1", // + "c/d2", // + ]); - //0 a/b/ - //2 b1 - //3 c/ - //4 d1 - //5 d2 + //0 a/b/ + //2 b1 + //3 c/ + //4 d1 + //5 d2 - // Set up test terminal - let test_backend = tui::backend::TestBackend::new(100, 100); - let mut terminal = tui::Terminal::new(test_backend) - .expect("Unable to set up terminal"); - let mut frame = terminal.get_frame(); + // Set up test terminal + let test_backend = tui::backend::TestBackend::new(100, 100); + let mut terminal = tui::Terminal::new(test_backend) + .expect("Unable to set up terminal"); + let mut frame = terminal.get_frame(); - // set up file tree - let mut ftc = FileTreeComponent::new( - "title", - true, - None, - SharedTheme::default(), - SharedKeyConfig::default(), - ); - ftc.update(&items) - .expect("Updating FileTreeComponent failed"); + // set up file tree + let mut ftc = FileTreeComponent::new( + "title", + true, + None, + SharedTheme::default(), + SharedKeyConfig::default(), + ); + ftc.update(&items) + .expect("Updating FileTreeComponent failed"); - ftc.move_selection(MoveSelection::Left); // Fold a/b/ - ftc.move_selection(MoveSelection::Down); // Move to c/ + ftc.move_selection(MoveSelection::Left); // Fold a/b/ + ftc.move_selection(MoveSelection::Down); // Move to c/ - ftc.draw(&mut frame, Rect::new(0, 0, 10, 5)) - .expect("Draw failed"); + ftc.draw(&mut frame, Rect::new(0, 0, 10, 5)) + .expect("Draw failed"); - assert_eq!(ftc.scroll_top.get(), 0); // should still be at top - } + assert_eq!(ftc.scroll_top.get(), 0); // should still be at top + } } diff --git a/src/components/help.rs b/src/components/help.rs index 6715f8d8..3a4ea6ea 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -1,6 +1,6 @@ use super::{ - visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, EventState, + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, EventState, }; use crate::{keys::SharedKeyConfig, strings, ui, version::Version}; use anyhow::Result; @@ -9,239 +9,239 @@ use crossterm::event::Event; use itertools::Itertools; use std::{borrow::Cow, cmp, convert::TryFrom}; use tui::{ - backend::Backend, - layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Modifier, Style}, - text::{Span, Spans}, - widgets::{Block, BorderType, Borders, Clear, Paragraph}, - Frame, + backend::Backend, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Modifier, Style}, + text::{Span, Spans}, + widgets::{Block, BorderType, Borders, Clear, Paragraph}, + Frame, }; use ui::style::SharedTheme; /// pub struct HelpComponent { - cmds: Vec, - visible: bool, - selection: u16, - theme: SharedTheme, - key_config: SharedKeyConfig, + cmds: Vec, + visible: bool, + selection: u16, + theme: SharedTheme, + key_config: SharedKeyConfig, } impl DrawableComponent for HelpComponent { - fn draw( - &self, - f: &mut Frame, - _rect: Rect, - ) -> Result<()> { - if self.visible { - const SIZE: (u16, u16) = (65, 24); - let scroll_threshold = SIZE.1 / 3; - let scroll = - self.selection.saturating_sub(scroll_threshold); + fn draw( + &self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { + if self.visible { + const SIZE: (u16, u16) = (65, 24); + let scroll_threshold = SIZE.1 / 3; + let scroll = + self.selection.saturating_sub(scroll_threshold); - let area = - ui::centered_rect_absolute(SIZE.0, SIZE.1, f.size()); + let area = + ui::centered_rect_absolute(SIZE.0, SIZE.1, f.size()); - f.render_widget(Clear, area); - f.render_widget( - Block::default() - .title(strings::help_title(&self.key_config)) - .borders(Borders::ALL) - .border_type(BorderType::Thick), - area, - ); + f.render_widget(Clear, area); + f.render_widget( + Block::default() + .title(strings::help_title(&self.key_config)) + .borders(Borders::ALL) + .border_type(BorderType::Thick), + area, + ); - let chunks = Layout::default() - .vertical_margin(1) - .horizontal_margin(1) - .direction(Direction::Vertical) - .constraints( - [Constraint::Min(1), Constraint::Length(1)] - .as_ref(), - ) - .split(area); + let chunks = Layout::default() + .vertical_margin(1) + .horizontal_margin(1) + .direction(Direction::Vertical) + .constraints( + [Constraint::Min(1), Constraint::Length(1)] + .as_ref(), + ) + .split(area); - f.render_widget( - Paragraph::new(self.get_text()) - .scroll((scroll, 0)) - .alignment(Alignment::Left), - chunks[0], - ); + f.render_widget( + Paragraph::new(self.get_text()) + .scroll((scroll, 0)) + .alignment(Alignment::Left), + chunks[0], + ); - f.render_widget( - Paragraph::new(Spans::from(vec![Span::styled( - Cow::from(format!("gitui {}", Version::new(),)), - Style::default(), - )])) - .alignment(Alignment::Right), - chunks[1], - ); - } + f.render_widget( + Paragraph::new(Spans::from(vec![Span::styled( + Cow::from(format!("gitui {}", Version::new(),)), + Style::default(), + )])) + .alignment(Alignment::Right), + chunks[1], + ); + } - Ok(()) - } + Ok(()) + } } impl Component for HelpComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - // only if help is open we have no other commands available - if self.visible && !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + // only if help is open we have no other commands available + if self.visible && !force_all { + out.clear(); + } - if self.visible { - out.push(CommandInfo::new( - strings::commands::scroll(&self.key_config), - true, - true, - )); + if self.visible { + out.push(CommandInfo::new( + strings::commands::scroll(&self.key_config), + true, + true, + )); - out.push(CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + )); + } - if !self.visible || force_all { - out.push( - CommandInfo::new( - strings::commands::help_open(&self.key_config), - true, - true, - ) - .order(99), - ); - } + if !self.visible || force_all { + out.push( + CommandInfo::new( + strings::commands::help_open(&self.key_config), + true, + true, + ) + .order(99), + ); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - } else if e == self.key_config.move_down { - self.move_selection(true); - } else if e == self.key_config.move_up { - self.move_selection(false); - } else { - } - } + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + } else if e == self.key_config.move_down { + self.move_selection(true); + } else if e == self.key_config.move_up { + self.move_selection(false); + } else { + } + } - Ok(EventState::Consumed) - } else if let Event::Key(k) = ev { - if k == self.key_config.open_help { - self.show()?; - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - } - } else { - Ok(EventState::NotConsumed) - } - } + Ok(EventState::Consumed) + } else if let Event::Key(k) = ev { + if k == self.key_config.open_help { + self.show()?; + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + } + } else { + Ok(EventState::NotConsumed) + } + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } impl HelpComponent { - pub const fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - cmds: vec![], - visible: false, - selection: 0, - theme, - key_config, - } - } - /// - pub fn set_cmds(&mut self, cmds: Vec) { - self.cmds = cmds - .into_iter() - .filter(|e| !e.text.hide_help) - .collect::>(); - self.cmds.sort_by_key(|e| e.text.clone()); - self.cmds.dedup_by_key(|e| e.text.clone()); - self.cmds.sort_by_key(|e| hash(&e.text.group)); - } + pub const fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + cmds: vec![], + visible: false, + selection: 0, + theme, + key_config, + } + } + /// + pub fn set_cmds(&mut self, cmds: Vec) { + self.cmds = cmds + .into_iter() + .filter(|e| !e.text.hide_help) + .collect::>(); + self.cmds.sort_by_key(|e| e.text.clone()); + self.cmds.dedup_by_key(|e| e.text.clone()); + self.cmds.sort_by_key(|e| hash(&e.text.group)); + } - fn move_selection(&mut self, inc: bool) { - let mut new_selection = self.selection; + fn move_selection(&mut self, inc: bool) { + let mut new_selection = self.selection; - new_selection = if inc { - new_selection.saturating_add(1) - } else { - new_selection.saturating_sub(1) - }; - new_selection = cmp::max(new_selection, 0); + new_selection = if inc { + new_selection.saturating_add(1) + } else { + new_selection.saturating_sub(1) + }; + new_selection = cmp::max(new_selection, 0); - if let Ok(max) = - u16::try_from(self.cmds.len().saturating_sub(1)) - { - self.selection = cmp::min(new_selection, max); - } - } + if let Ok(max) = + u16::try_from(self.cmds.len().saturating_sub(1)) + { + self.selection = cmp::min(new_selection, max); + } + } - fn get_text(&self) -> Vec { - let mut txt: Vec = Vec::new(); + fn get_text(&self) -> Vec { + let mut txt: Vec = Vec::new(); - let mut processed = 0_u16; + let mut processed = 0_u16; - for (key, group) in - &self.cmds.iter().group_by(|e| e.text.group) - { - txt.push(Spans::from(Span::styled( - Cow::from(key.to_string()), - Style::default().add_modifier(Modifier::REVERSED), - ))); + for (key, group) in + &self.cmds.iter().group_by(|e| e.text.group) + { + txt.push(Spans::from(Span::styled( + Cow::from(key.to_string()), + Style::default().add_modifier(Modifier::REVERSED), + ))); - for command_info in group { - let is_selected = self.selection == processed; + for command_info in group { + let is_selected = self.selection == processed; - processed += 1; + processed += 1; - txt.push(Spans::from(Span::styled( - Cow::from(if is_selected { - format!(">{}", command_info.text.name) - } else { - format!(" {}", command_info.text.name) - }), - self.theme.text(true, is_selected), - ))); + txt.push(Spans::from(Span::styled( + Cow::from(if is_selected { + format!(">{}", command_info.text.name) + } else { + format!(" {}", command_info.text.name) + }), + self.theme.text(true, is_selected), + ))); - if is_selected { - txt.push(Spans::from(Span::styled( - Cow::from(format!( - " {}\n", - command_info.text.desc - )), - self.theme.text(true, is_selected), - ))); - } - } - } + if is_selected { + txt.push(Spans::from(Span::styled( + Cow::from(format!( + " {}\n", + command_info.text.desc + )), + self.theme.text(true, is_selected), + ))); + } + } + } - txt - } + txt + } } diff --git a/src/components/inspect_commit.rs b/src/components/inspect_commit.rs index 397ea943..ae910486 100644 --- a/src/components/inspect_commit.rs +++ b/src/components/inspect_commit.rs @@ -1,281 +1,281 @@ use super::{ - command_pump, event_pump, visibility_blocking, CommandBlocking, - CommandInfo, CommitDetailsComponent, Component, DiffComponent, - DrawableComponent, EventState, + command_pump, event_pump, visibility_blocking, CommandBlocking, + CommandInfo, CommitDetailsComponent, Component, DiffComponent, + DrawableComponent, EventState, }; use crate::{ - accessors, - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings, - ui::style::SharedTheme, + accessors, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{CommitId, CommitTags}, - AsyncDiff, AsyncGitNotification, DiffParams, DiffType, + sync::{CommitId, CommitTags}, + AsyncDiff, AsyncGitNotification, DiffParams, DiffType, }; use crossbeam_channel::Sender; use crossterm::event::Event; use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - widgets::Clear, - Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + widgets::Clear, + Frame, }; pub struct InspectCommitComponent { - queue: Queue, - commit_id: Option, - tags: Option, - diff: DiffComponent, - details: CommitDetailsComponent, - git_diff: AsyncDiff, - visible: bool, - key_config: SharedKeyConfig, + queue: Queue, + commit_id: Option, + tags: Option, + diff: DiffComponent, + details: CommitDetailsComponent, + git_diff: AsyncDiff, + visible: bool, + key_config: SharedKeyConfig, } impl DrawableComponent for InspectCommitComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.is_visible() { - let percentages = if self.diff.focused() { - (30, 70) - } else { - (50, 50) - }; + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.is_visible() { + let percentages = if self.diff.focused() { + (30, 70) + } else { + (50, 50) + }; - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage(percentages.0), - Constraint::Percentage(percentages.1), - ] - .as_ref(), - ) - .split(rect); + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(percentages.0), + Constraint::Percentage(percentages.1), + ] + .as_ref(), + ) + .split(rect); - f.render_widget(Clear, rect); + f.render_widget(Clear, rect); - self.details.draw(f, chunks[0])?; - self.diff.draw(f, chunks[1])?; - } + self.details.draw(f, chunks[0])?; + self.diff.draw(f, chunks[1])?; + } - Ok(()) - } + Ok(()) + } } impl Component for InspectCommitComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - command_pump( - out, - force_all, - self.components().as_slice(), - ); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + command_pump( + out, + force_all, + self.components().as_slice(), + ); - out.push( - CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - ) - .order(1), - ); + out.push( + CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + ) + .order(1), + ); - out.push(CommandInfo::new( - strings::commands::diff_focus_right(&self.key_config), - self.can_focus_diff(), - !self.diff.focused() || force_all, - )); + out.push(CommandInfo::new( + strings::commands::diff_focus_right(&self.key_config), + self.can_focus_diff(), + !self.diff.focused() || force_all, + )); - out.push(CommandInfo::new( - strings::commands::diff_focus_left(&self.key_config), - true, - self.diff.focused() || force_all, - )); + out.push(CommandInfo::new( + strings::commands::diff_focus_left(&self.key_config), + true, + self.diff.focused() || force_all, + )); - out.push(CommandInfo::new( - strings::commands::inspect_file_tree( - &self.key_config, - ), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::inspect_file_tree( + &self.key_config, + ), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { - if event_pump(ev, self.components_mut().as_mut_slice())? - .is_consumed() - { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if event_pump(ev, self.components_mut().as_mut_slice())? + .is_consumed() + { + return Ok(EventState::Consumed); + } - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - } else if e == self.key_config.focus_right - && self.can_focus_diff() - { - self.details.focus(false); - self.diff.focus(true); - } else if e == self.key_config.focus_left - && self.diff.focused() - { - self.details.focus(true); - self.diff.focus(false); - } else if e == self.key_config.open_file_tree { - if let Some(commit) = self.commit_id { - self.queue.push(InternalEvent::OpenFileTree( - commit, - )); - self.hide(); - } - } else if e == self.key_config.focus_left { - self.hide(); - } + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + } else if e == self.key_config.focus_right + && self.can_focus_diff() + { + self.details.focus(false); + self.diff.focus(true); + } else if e == self.key_config.focus_left + && self.diff.focused() + { + self.details.focus(true); + self.diff.focus(false); + } else if e == self.key_config.open_file_tree { + if let Some(commit) = self.commit_id { + self.queue.push(InternalEvent::OpenFileTree( + commit, + )); + self.hide(); + } + } else if e == self.key_config.focus_left { + self.hide(); + } - return Ok(EventState::Consumed); - } - } + return Ok(EventState::Consumed); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } - fn hide(&mut self) { - self.visible = false; - } - fn show(&mut self) -> Result<()> { - self.visible = true; - self.details.show()?; - self.details.focus(true); - self.diff.focus(false); - self.update()?; - Ok(()) - } + fn is_visible(&self) -> bool { + self.visible + } + fn hide(&mut self) { + self.visible = false; + } + fn show(&mut self) -> Result<()> { + self.visible = true; + self.details.show()?; + self.details.focus(true); + self.diff.focus(false); + self.update()?; + Ok(()) + } } impl InspectCommitComponent { - accessors!(self, [diff, details]); + accessors!(self, [diff, details]); - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - details: CommitDetailsComponent::new( - queue, - sender, - theme.clone(), - key_config.clone(), - ), - diff: DiffComponent::new( - queue.clone(), - theme, - key_config.clone(), - true, - ), - commit_id: None, - tags: None, - git_diff: AsyncDiff::new(sender), - visible: false, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + details: CommitDetailsComponent::new( + queue, + sender, + theme.clone(), + key_config.clone(), + ), + diff: DiffComponent::new( + queue.clone(), + theme, + key_config.clone(), + true, + ), + commit_id: None, + tags: None, + git_diff: AsyncDiff::new(sender), + visible: false, + key_config, + } + } - /// - pub fn open( - &mut self, - id: CommitId, - tags: Option, - ) -> Result<()> { - self.commit_id = Some(id); - self.tags = tags; - self.show()?; + /// + pub fn open( + &mut self, + id: CommitId, + tags: Option, + ) -> Result<()> { + self.commit_id = Some(id); + self.tags = tags; + self.show()?; - Ok(()) - } + Ok(()) + } - /// - pub fn any_work_pending(&self) -> bool { - self.git_diff.is_pending() || self.details.any_work_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.git_diff.is_pending() || self.details.any_work_pending() + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::CommitFiles = ev { - self.update()?; - } else if let AsyncGitNotification::Diff = ev { - self.update_diff()?; - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::CommitFiles = ev { + self.update()?; + } else if let AsyncGitNotification::Diff = ev { + self.update_diff()?; + } + } - Ok(()) - } + Ok(()) + } - /// called when any tree component changed selection - pub fn update_diff(&mut self) -> Result<()> { - if self.is_visible() { - if let Some(id) = self.commit_id { - if let Some(f) = self.details.files().selection_file() - { - let diff_params = DiffParams { - path: f.path.clone(), - diff_type: DiffType::Commit(id), - }; + /// called when any tree component changed selection + pub fn update_diff(&mut self) -> Result<()> { + if self.is_visible() { + if let Some(id) = self.commit_id { + if let Some(f) = self.details.files().selection_file() + { + let diff_params = DiffParams { + path: f.path.clone(), + diff_type: DiffType::Commit(id), + }; - if let Some((params, last)) = - self.git_diff.last()? - { - if params == diff_params { - self.diff.update(f.path, false, last); - return Ok(()); - } - } + if let Some((params, last)) = + self.git_diff.last()? + { + if params == diff_params { + self.diff.update(f.path, false, last); + return Ok(()); + } + } - self.git_diff.request(diff_params)?; - self.diff.clear(true); - return Ok(()); - } - } + self.git_diff.request(diff_params)?; + self.diff.clear(true); + return Ok(()); + } + } - self.diff.clear(false); - } + self.diff.clear(false); + } - Ok(()) - } + Ok(()) + } - fn update(&mut self) -> Result<()> { - self.details.set_commit(self.commit_id, self.tags.clone())?; - self.update_diff()?; + fn update(&mut self) -> Result<()> { + self.details.set_commit(self.commit_id, self.tags.clone())?; + self.update_diff()?; - Ok(()) - } + Ok(()) + } - fn can_focus_diff(&self) -> bool { - self.details.files().selection_file().is_some() - } + fn can_focus_diff(&self) -> bool { + self.details.files().selection_file().is_some() + } } diff --git a/src/components/mod.rs b/src/components/mod.rs index b7bd257d..d9d0916b 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -60,11 +60,11 @@ use anyhow::Result; use crossterm::event::Event; use std::convert::From; use tui::{ - backend::Backend, - layout::{Alignment, Rect}, - text::{Span, Text}, - widgets::{Block, BorderType, Borders, Paragraph, Wrap}, - Frame, + backend::Backend, + layout::{Alignment, Rect}, + text::{Span, Text}, + widgets::{Block, BorderType, Borders, Paragraph, Wrap}, + Frame, }; /// creates accessors for a list of components @@ -134,175 +134,175 @@ macro_rules! setup_popups { /// returns `true` if event was consumed pub fn event_pump( - ev: Event, - components: &mut [&mut dyn Component], + ev: Event, + components: &mut [&mut dyn Component], ) -> Result { - for c in components { - if c.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } - } + for c in components { + if c.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } + } - Ok(EventState::NotConsumed) + Ok(EventState::NotConsumed) } /// helper fn to simplify delegating command /// gathering down into child components /// see `event_pump`,`accessors` pub fn command_pump( - out: &mut Vec, - force_all: bool, - components: &[&dyn Component], + out: &mut Vec, + force_all: bool, + components: &[&dyn Component], ) { - for c in components { - if c.commands(out, force_all) != CommandBlocking::PassingOn - && !force_all - { - break; - } - } + for c in components { + if c.commands(out, force_all) != CommandBlocking::PassingOn + && !force_all + { + break; + } + } } #[derive(Copy, Clone)] pub enum ScrollType { - Up, - Down, - Home, - End, - PageUp, - PageDown, + Up, + Down, + Home, + End, + PageUp, + PageDown, } #[derive(Copy, Clone)] pub enum Direction { - Up, - Down, + Up, + Down, } /// #[derive(PartialEq)] pub enum CommandBlocking { - Blocking, - PassingOn, + Blocking, + PassingOn, } /// pub fn visibility_blocking( - comp: &T, + comp: &T, ) -> CommandBlocking { - if comp.is_visible() { - CommandBlocking::Blocking - } else { - CommandBlocking::PassingOn - } + if comp.is_visible() { + CommandBlocking::Blocking + } else { + CommandBlocking::PassingOn + } } /// pub trait DrawableComponent { - /// - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()>; + /// + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()>; } /// #[derive(PartialEq)] pub enum EventState { - Consumed, - NotConsumed, + Consumed, + NotConsumed, } impl EventState { - pub fn is_consumed(&self) -> bool { - *self == Self::Consumed - } + pub fn is_consumed(&self) -> bool { + *self == Self::Consumed + } } impl From for EventState { - fn from(consumed: bool) -> Self { - if consumed { - Self::Consumed - } else { - Self::NotConsumed - } - } + fn from(consumed: bool) -> Self { + if consumed { + Self::Consumed + } else { + Self::NotConsumed + } + } } /// base component trait pub trait Component { - /// - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking; + /// + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking; - /// - fn event(&mut self, ev: Event) -> Result; + /// + fn event(&mut self, ev: Event) -> Result; - /// - fn focused(&self) -> bool { - false - } - /// focus/unfocus this component depending on param - fn focus(&mut self, _focus: bool) {} - /// - fn is_visible(&self) -> bool { - true - } - /// - fn hide(&mut self) {} - /// - fn show(&mut self) -> Result<()> { - Ok(()) - } + /// + fn focused(&self) -> bool { + false + } + /// focus/unfocus this component depending on param + fn focus(&mut self, _focus: bool) {} + /// + fn is_visible(&self) -> bool { + true + } + /// + fn hide(&mut self) {} + /// + fn show(&mut self) -> Result<()> { + Ok(()) + } - /// - fn toggle_visible(&mut self) -> Result<()> { - if self.is_visible() { - self.hide(); - Ok(()) - } else { - self.show() - } - } + /// + fn toggle_visible(&mut self) -> Result<()> { + if self.is_visible() { + self.hide(); + Ok(()) + } else { + self.show() + } + } } fn dialog_paragraph<'a>( - title: &'a str, - content: Text<'a>, - theme: &Theme, - focused: bool, + title: &'a str, + content: Text<'a>, + theme: &Theme, + focused: bool, ) -> Paragraph<'a> { - Paragraph::new(content) - .block( - Block::default() - .title(Span::styled(title, theme.title(focused))) - .borders(Borders::ALL) - .border_style(theme.block(focused)), - ) - .alignment(Alignment::Left) + Paragraph::new(content) + .block( + Block::default() + .title(Span::styled(title, theme.title(focused))) + .borders(Borders::ALL) + .border_style(theme.block(focused)), + ) + .alignment(Alignment::Left) } fn popup_paragraph<'a, T>( - title: &'a str, - content: T, - theme: &Theme, - focused: bool, + title: &'a str, + content: T, + theme: &Theme, + focused: bool, ) -> Paragraph<'a> where - T: Into>, + T: Into>, { - Paragraph::new(content.into()) - .block( - Block::default() - .title(Span::styled(title, theme.title(focused))) - .borders(Borders::ALL) - .border_type(BorderType::Thick) - .border_style(theme.block(focused)), - ) - .alignment(Alignment::Left) - .wrap(Wrap { trim: true }) + Paragraph::new(content.into()) + .block( + Block::default() + .title(Span::styled(title, theme.title(focused))) + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .border_style(theme.block(focused)), + ) + .alignment(Alignment::Left) + .wrap(Wrap { trim: true }) } diff --git a/src/components/msg.rs b/src/components/msg.rs index 606949a4..33242fbd 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -1,142 +1,142 @@ use super::{ - visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, EventState, + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, EventState, }; use crate::{keys::SharedKeyConfig, strings, ui}; use crossterm::event::Event; use std::convert::TryFrom; use tui::{ - backend::Backend, - layout::{Alignment, Rect}, - text::Span, - widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap}, - Frame, + backend::Backend, + layout::{Alignment, Rect}, + text::Span, + widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap}, + Frame, }; use ui::style::SharedTheme; pub struct MsgComponent { - title: String, - msg: String, - visible: bool, - theme: SharedTheme, - key_config: SharedKeyConfig, + title: String, + msg: String, + visible: bool, + theme: SharedTheme, + key_config: SharedKeyConfig, } use anyhow::Result; impl DrawableComponent for MsgComponent { - fn draw( - &self, - f: &mut Frame, - _rect: Rect, - ) -> Result<()> { - if !self.visible { - return Ok(()); - } + fn draw( + &self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { + if !self.visible { + return Ok(()); + } - // determine the maximum width of text block - let lens = self - .msg - .split('\n') - .map(str::len) - .collect::>(); - let mut max = lens.iter().max().expect("max") + 2; - if max > std::u16::MAX as usize { - max = std::u16::MAX as usize; - } - let mut width = - u16::try_from(max).expect("cant fail due to check above"); - // dont overflow screen, and dont get too narrow - if width > f.size().width { - width = f.size().width; - } else if width < 60 { - width = 60; - } + // determine the maximum width of text block + let lens = self + .msg + .split('\n') + .map(str::len) + .collect::>(); + let mut max = lens.iter().max().expect("max") + 2; + if max > std::u16::MAX as usize { + max = std::u16::MAX as usize; + } + let mut width = + u16::try_from(max).expect("cant fail due to check above"); + // dont overflow screen, and dont get too narrow + if width > f.size().width { + width = f.size().width; + } else if width < 60 { + width = 60; + } - let area = ui::centered_rect_absolute(width, 25, f.size()); - f.render_widget(Clear, area); - f.render_widget( - Paragraph::new(self.msg.clone()) - .block( - Block::default() - .title(Span::styled( - self.title.as_str(), - self.theme.text_danger(), - )) - .borders(Borders::ALL) - .border_type(BorderType::Thick), - ) - .alignment(Alignment::Left) - .wrap(Wrap { trim: true }), - area, - ); + let area = ui::centered_rect_absolute(width, 25, f.size()); + f.render_widget(Clear, area); + f.render_widget( + Paragraph::new(self.msg.clone()) + .block( + Block::default() + .title(Span::styled( + self.title.as_str(), + self.theme.text_danger(), + )) + .borders(Borders::ALL) + .border_type(BorderType::Thick), + ) + .alignment(Alignment::Left) + .wrap(Wrap { trim: true }), + area, + ); - Ok(()) - } + Ok(()) + } } impl Component for MsgComponent { - fn commands( - &self, - out: &mut Vec, - _force_all: bool, - ) -> CommandBlocking { - out.push(CommandInfo::new( - strings::commands::close_msg(&self.key_config), - true, - self.visible, - )); + fn commands( + &self, + out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + out.push(CommandInfo::new( + strings::commands::close_msg(&self.key_config), + true, + self.visible, + )); - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.enter { - self.hide(); - } - } - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - } - } + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.enter { + self.hide(); + } + } + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + } + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } impl MsgComponent { - pub const fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - title: String::new(), - msg: String::new(), - visible: false, - theme, - key_config, - } - } + pub const fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + title: String::new(), + msg: String::new(), + visible: false, + theme, + key_config, + } + } - /// - pub fn show_error(&mut self, msg: &str) -> Result<()> { - self.title = strings::msg_title_error(&self.key_config); - self.msg = msg.to_string(); - self.show()?; + /// + pub fn show_error(&mut self, msg: &str) -> Result<()> { + self.title = strings::msg_title_error(&self.key_config); + self.msg = msg.to_string(); + self.show()?; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/pull.rs b/src/components/pull.rs index 552a7eec..05b1b7a7 100644 --- a/src/components/pull.rs +++ b/src/components/pull.rs @@ -1,293 +1,293 @@ use super::PushComponent; use crate::{ - components::{ - cred::CredComponent, visibility_blocking, CommandBlocking, - CommandInfo, Component, DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, Queue}, - strings, try_or_popup, - ui::{self, style::SharedTheme}, + components::{ + cred::CredComponent, visibility_blocking, CommandBlocking, + CommandInfo, Component, DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, Queue}, + strings, try_or_popup, + ui::{self, style::SharedTheme}, }; use anyhow::Result; use asyncgit::{ - sync::{ - self, - cred::{ - extract_username_password, need_username_password, - BasicAuthCredential, - }, - get_default_remote, - }, - AsyncFetch, AsyncGitNotification, FetchRequest, RemoteProgress, - CWD, + sync::{ + self, + cred::{ + extract_username_password, need_username_password, + BasicAuthCredential, + }, + get_default_remote, + }, + AsyncFetch, AsyncGitNotification, FetchRequest, RemoteProgress, + CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use tui::{ - backend::Backend, - layout::Rect, - text::Span, - widgets::{Block, BorderType, Borders, Clear, Gauge}, - Frame, + backend::Backend, + layout::Rect, + text::Span, + widgets::{Block, BorderType, Borders, Clear, Gauge}, + Frame, }; /// pub struct PullComponent { - visible: bool, - git_fetch: AsyncFetch, - progress: Option, - pending: bool, - branch: String, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - input_cred: CredComponent, + visible: bool, + git_fetch: AsyncFetch, + progress: Option, + pending: bool, + branch: String, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + input_cred: CredComponent, } impl PullComponent { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - pending: false, - visible: false, - branch: String::new(), - git_fetch: AsyncFetch::new(sender), - progress: None, - input_cred: CredComponent::new( - theme.clone(), - key_config.clone(), - ), - theme, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + pending: false, + visible: false, + branch: String::new(), + git_fetch: AsyncFetch::new(sender), + progress: None, + input_cred: CredComponent::new( + theme.clone(), + key_config.clone(), + ), + theme, + key_config, + } + } - /// - pub fn fetch(&mut self, branch: String) -> Result<()> { - self.branch = branch; - self.show()?; - if need_username_password()? { - let cred = - extract_username_password().unwrap_or_else(|_| { - BasicAuthCredential::new(None, None) - }); - if cred.is_complete() { - self.fetch_from_remote(Some(cred)) - } else { - self.input_cred.set_cred(cred); - self.input_cred.show() - } - } else { - self.fetch_from_remote(None) - } - } + /// + pub fn fetch(&mut self, branch: String) -> Result<()> { + self.branch = branch; + self.show()?; + if need_username_password()? { + let cred = + extract_username_password().unwrap_or_else(|_| { + BasicAuthCredential::new(None, None) + }); + if cred.is_complete() { + self.fetch_from_remote(Some(cred)) + } else { + self.input_cred.set_cred(cred); + self.input_cred.show() + } + } else { + self.fetch_from_remote(None) + } + } - fn fetch_from_remote( - &mut self, - cred: Option, - ) -> Result<()> { - self.pending = true; - self.progress = None; - self.git_fetch.request(FetchRequest { - remote: get_default_remote(CWD)?, - branch: self.branch.clone(), - basic_credential: cred, - })?; + fn fetch_from_remote( + &mut self, + cred: Option, + ) -> Result<()> { + self.pending = true; + self.progress = None; + self.git_fetch.request(FetchRequest { + remote: get_default_remote(CWD)?, + branch: self.branch.clone(), + basic_credential: cred, + })?; - Ok(()) - } + Ok(()) + } - /// - pub const fn any_work_pending(&self) -> bool { - self.pending - } + /// + pub const fn any_work_pending(&self) -> bool { + self.pending + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::Fetch = ev { - self.update()?; - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::Fetch = ev { + self.update()?; + } + } - Ok(()) - } + Ok(()) + } - /// - fn update(&mut self) -> Result<()> { - self.pending = self.git_fetch.is_pending()?; - self.progress = self.git_fetch.progress()?; + /// + fn update(&mut self) -> Result<()> { + self.pending = self.git_fetch.is_pending()?; + self.progress = self.git_fetch.progress()?; - if !self.pending { - if let Some((_bytes, err)) = - self.git_fetch.last_result()? - { - if err.is_empty() { - self.try_ff_merge()?; - } else { - self.pending = false; - self.hide(); - self.queue.push(InternalEvent::ShowErrorMsg( - format!("fetch failed:\n{}", err), - )); - } - } - } + if !self.pending { + if let Some((_bytes, err)) = + self.git_fetch.last_result()? + { + if err.is_empty() { + self.try_ff_merge()?; + } else { + self.pending = false; + self.hide(); + self.queue.push(InternalEvent::ShowErrorMsg( + format!("fetch failed:\n{}", err), + )); + } + } + } - Ok(()) - } + Ok(()) + } - // check if something is incoming and try a ff merge then - fn try_ff_merge(&mut self) -> Result<()> { - let branch_compare = - sync::branch_compare_upstream(CWD, &self.branch)?; - if branch_compare.behind > 0 { - let ff_res = sync::branch_merge_upstream_fastforward( - CWD, - &self.branch, - ); - if let Err(err) = ff_res { - log::trace!("ff failed: {}", err); - self.confirm_merge(branch_compare.behind); - } - } + // check if something is incoming and try a ff merge then + fn try_ff_merge(&mut self) -> Result<()> { + let branch_compare = + sync::branch_compare_upstream(CWD, &self.branch)?; + if branch_compare.behind > 0 { + let ff_res = sync::branch_merge_upstream_fastforward( + CWD, + &self.branch, + ); + if let Err(err) = ff_res { + log::trace!("ff failed: {}", err); + self.confirm_merge(branch_compare.behind); + } + } - self.hide(); + self.hide(); - Ok(()) - } + Ok(()) + } - pub fn try_conflict_free_merge(&self, rebase: bool) { - if rebase { - try_or_popup!( - self, - "rebase failed:", - sync::merge_upstream_rebase(CWD, &self.branch) - ); - } else { - try_or_popup!( - self, - "merge failed:", - sync::merge_upstream_commit(CWD, &self.branch) - ); - } - } + pub fn try_conflict_free_merge(&self, rebase: bool) { + if rebase { + try_or_popup!( + self, + "rebase failed:", + sync::merge_upstream_rebase(CWD, &self.branch) + ); + } else { + try_or_popup!( + self, + "merge failed:", + sync::merge_upstream_commit(CWD, &self.branch) + ); + } + } - fn confirm_merge(&mut self, incoming: usize) { - self.queue.push(InternalEvent::ConfirmAction( - Action::PullMerge { - incoming, - rebase: sync::config_is_pull_rebase(CWD) - .unwrap_or_default(), - }, - )); - self.hide(); - } + fn confirm_merge(&mut self, incoming: usize) { + self.queue.push(InternalEvent::ConfirmAction( + Action::PullMerge { + incoming, + rebase: sync::config_is_pull_rebase(CWD) + .unwrap_or_default(), + }, + )); + self.hide(); + } } impl DrawableComponent for PullComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.visible { - let (state, progress) = - PushComponent::get_progress(&self.progress); + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.visible { + let (state, progress) = + PushComponent::get_progress(&self.progress); - let area = ui::centered_rect_absolute(30, 3, f.size()); + let area = ui::centered_rect_absolute(30, 3, f.size()); - f.render_widget(Clear, area); - f.render_widget( - Gauge::default() - .label(state.as_str()) - .block( - Block::default() - .title(Span::styled( - strings::PULL_POPUP_MSG, - self.theme.title(true), - )) - .borders(Borders::ALL) - .border_type(BorderType::Thick) - .border_style(self.theme.block(true)), - ) - .gauge_style(self.theme.push_gauge()) - .percent(u16::from(progress)), - area, - ); - self.input_cred.draw(f, rect)?; - } + f.render_widget(Clear, area); + f.render_widget( + Gauge::default() + .label(state.as_str()) + .block( + Block::default() + .title(Span::styled( + strings::PULL_POPUP_MSG, + self.theme.title(true), + )) + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .border_style(self.theme.block(true)), + ) + .gauge_style(self.theme.push_gauge()) + .percent(u16::from(progress)), + area, + ); + self.input_cred.draw(f, rect)?; + } - Ok(()) - } + Ok(()) + } } impl Component for PullComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - if !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + if !force_all { + out.clear(); + } - if self.input_cred.is_visible() { - return self.input_cred.commands(out, force_all); - } - out.push(CommandInfo::new( - strings::commands::close_msg(&self.key_config), - !self.pending, - self.visible, - )); - } + if self.input_cred.is_visible() { + return self.input_cred.commands(out, force_all); + } + out.push(CommandInfo::new( + strings::commands::close_msg(&self.key_config), + !self.pending, + self.visible, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(_) = ev { - if self.input_cred.is_visible() { - self.input_cred.event(ev)?; + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(_) = ev { + if self.input_cred.is_visible() { + self.input_cred.event(ev)?; - if self.input_cred.get_cred().is_complete() - || !self.input_cred.is_visible() - { - self.fetch_from_remote(Some( - self.input_cred.get_cred().clone(), - ))?; - self.input_cred.hide(); - } - } - } - return Ok(EventState::Consumed); - } - Ok(EventState::NotConsumed) - } + if self.input_cred.get_cred().is_complete() + || !self.input_cred.is_visible() + { + self.fetch_from_remote(Some( + self.input_cred.get_cred().clone(), + ))?; + self.input_cred.hide(); + } + } + } + return Ok(EventState::Consumed); + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/push.rs b/src/components/push.rs index 12541d29..0dfe3a85 100644 --- a/src/components/push.rs +++ b/src/components/push.rs @@ -1,333 +1,333 @@ use crate::{ - components::{ - cred::CredComponent, visibility_blocking, CommandBlocking, - CommandInfo, Component, DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings, - ui::{self, style::SharedTheme}, + components::{ + cred::CredComponent, visibility_blocking, CommandBlocking, + CommandInfo, Component, DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::{self, style::SharedTheme}, }; use anyhow::Result; use asyncgit::{ - sync::{ - cred::{ - extract_username_password, need_username_password, - BasicAuthCredential, - }, - get_branch_remote, get_default_remote, - }, - AsyncGitNotification, AsyncPush, PushRequest, RemoteProgress, - RemoteProgressState, CWD, + sync::{ + cred::{ + extract_username_password, need_username_password, + BasicAuthCredential, + }, + get_branch_remote, get_default_remote, + }, + AsyncGitNotification, AsyncPush, PushRequest, RemoteProgress, + RemoteProgressState, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use tui::{ - backend::Backend, - layout::Rect, - text::Span, - widgets::{Block, BorderType, Borders, Clear, Gauge}, - Frame, + backend::Backend, + layout::Rect, + text::Span, + widgets::{Block, BorderType, Borders, Clear, Gauge}, + Frame, }; /// #[derive(PartialEq, Eq)] enum PushComponentModifier { - None, - Force, - Delete, - ForceDelete, + None, + Force, + Delete, + ForceDelete, } impl PushComponentModifier { - pub(crate) fn force(&self) -> bool { - self == &Self::Force || self == &Self::ForceDelete - } - pub(crate) fn delete(&self) -> bool { - self == &Self::Delete || self == &Self::ForceDelete - } + pub(crate) fn force(&self) -> bool { + self == &Self::Force || self == &Self::ForceDelete + } + pub(crate) fn delete(&self) -> bool { + self == &Self::Delete || self == &Self::ForceDelete + } } /// pub struct PushComponent { - modifier: PushComponentModifier, - visible: bool, - git_push: AsyncPush, - progress: Option, - pending: bool, - branch: String, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - input_cred: CredComponent, + modifier: PushComponentModifier, + visible: bool, + git_push: AsyncPush, + progress: Option, + pending: bool, + branch: String, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + input_cred: CredComponent, } impl PushComponent { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - modifier: PushComponentModifier::None, - pending: false, - visible: false, - branch: String::new(), - git_push: AsyncPush::new(sender), - progress: None, - input_cred: CredComponent::new( - theme.clone(), - key_config.clone(), - ), - theme, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + modifier: PushComponentModifier::None, + pending: false, + visible: false, + branch: String::new(), + git_push: AsyncPush::new(sender), + progress: None, + input_cred: CredComponent::new( + theme.clone(), + key_config.clone(), + ), + theme, + key_config, + } + } - /// - pub fn push( - &mut self, - branch: String, - force: bool, - delete: bool, - ) -> Result<()> { - self.branch = branch; - self.modifier = match (force, delete) { - (true, true) => PushComponentModifier::ForceDelete, - (false, true) => PushComponentModifier::Delete, - (true, false) => PushComponentModifier::Force, - (false, false) => PushComponentModifier::None, - }; + /// + pub fn push( + &mut self, + branch: String, + force: bool, + delete: bool, + ) -> Result<()> { + self.branch = branch; + self.modifier = match (force, delete) { + (true, true) => PushComponentModifier::ForceDelete, + (false, true) => PushComponentModifier::Delete, + (true, false) => PushComponentModifier::Force, + (false, false) => PushComponentModifier::None, + }; - self.show()?; + self.show()?; - if need_username_password()? { - let cred = - extract_username_password().unwrap_or_else(|_| { - BasicAuthCredential::new(None, None) - }); - if cred.is_complete() { - self.push_to_remote(Some(cred), force) - } else { - self.input_cred.set_cred(cred); - self.input_cred.show() - } - } else { - self.push_to_remote(None, force) - } - } + if need_username_password()? { + let cred = + extract_username_password().unwrap_or_else(|_| { + BasicAuthCredential::new(None, None) + }); + if cred.is_complete() { + self.push_to_remote(Some(cred), force) + } else { + self.input_cred.set_cred(cred); + self.input_cred.show() + } + } else { + self.push_to_remote(None, force) + } + } - fn push_to_remote( - &mut self, - cred: Option, - force: bool, - ) -> Result<()> { - let remote = if let Ok(Some(remote)) = - get_branch_remote(CWD, &self.branch) - { - log::info!("push: branch '{}' has upstream for remote '{}' - using that",self.branch,remote); - remote - } else { - log::info!("push: branch '{}' has no upstream - looking up default remote",self.branch); - let remote = get_default_remote(CWD)?; - log::info!( - "push: branch '{}' to remote '{}'", - self.branch, - remote - ); - remote - }; + fn push_to_remote( + &mut self, + cred: Option, + force: bool, + ) -> Result<()> { + let remote = if let Ok(Some(remote)) = + get_branch_remote(CWD, &self.branch) + { + log::info!("push: branch '{}' has upstream for remote '{}' - using that",self.branch,remote); + remote + } else { + log::info!("push: branch '{}' has no upstream - looking up default remote",self.branch); + let remote = get_default_remote(CWD)?; + log::info!( + "push: branch '{}' to remote '{}'", + self.branch, + remote + ); + remote + }; - self.pending = true; - self.progress = None; - self.git_push.request(PushRequest { - remote, - branch: self.branch.clone(), - force, - delete: self.modifier.delete(), - basic_credential: cred, - })?; - Ok(()) - } + self.pending = true; + self.progress = None; + self.git_push.request(PushRequest { + remote, + branch: self.branch.clone(), + force, + delete: self.modifier.delete(), + basic_credential: cred, + })?; + Ok(()) + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::Push = ev { - self.update()?; - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::Push = ev { + self.update()?; + } + } - Ok(()) - } + Ok(()) + } - /// - fn update(&mut self) -> Result<()> { - self.pending = self.git_push.is_pending()?; - self.progress = self.git_push.progress()?; + /// + fn update(&mut self) -> Result<()> { + self.pending = self.git_push.is_pending()?; + self.progress = self.git_push.progress()?; - if !self.pending { - if let Some(err) = self.git_push.last_result()? { - self.queue.push(InternalEvent::ShowErrorMsg( - format!("push failed:\n{}", err), - )); - } - self.hide(); - } + if !self.pending { + if let Some(err) = self.git_push.last_result()? { + self.queue.push(InternalEvent::ShowErrorMsg( + format!("push failed:\n{}", err), + )); + } + self.hide(); + } - Ok(()) - } + Ok(()) + } - /// - pub const fn any_work_pending(&self) -> bool { - self.pending - } + /// + pub const fn any_work_pending(&self) -> bool { + self.pending + } - /// - pub fn get_progress( - progress: &Option, - ) -> (String, u8) { - progress.as_ref().map_or( - (strings::PUSH_POPUP_PROGRESS_NONE.into(), 0), - |progress| { - ( - Self::progress_state_name(&progress.state), - progress.get_progress_percent(), - ) - }, - ) - } + /// + pub fn get_progress( + progress: &Option, + ) -> (String, u8) { + progress.as_ref().map_or( + (strings::PUSH_POPUP_PROGRESS_NONE.into(), 0), + |progress| { + ( + Self::progress_state_name(&progress.state), + progress.get_progress_percent(), + ) + }, + ) + } - fn progress_state_name(state: &RemoteProgressState) -> String { - match state { - RemoteProgressState::PackingAddingObject => { - strings::PUSH_POPUP_STATES_ADDING - } - RemoteProgressState::PackingDeltafiction => { - strings::PUSH_POPUP_STATES_DELTAS - } - RemoteProgressState::Pushing => { - strings::PUSH_POPUP_STATES_PUSHING - } - RemoteProgressState::Transfer => { - strings::PUSH_POPUP_STATES_TRANSFER - } - RemoteProgressState::Done => { - strings::PUSH_POPUP_STATES_DONE - } - } - .into() - } + fn progress_state_name(state: &RemoteProgressState) -> String { + match state { + RemoteProgressState::PackingAddingObject => { + strings::PUSH_POPUP_STATES_ADDING + } + RemoteProgressState::PackingDeltafiction => { + strings::PUSH_POPUP_STATES_DELTAS + } + RemoteProgressState::Pushing => { + strings::PUSH_POPUP_STATES_PUSHING + } + RemoteProgressState::Transfer => { + strings::PUSH_POPUP_STATES_TRANSFER + } + RemoteProgressState::Done => { + strings::PUSH_POPUP_STATES_DONE + } + } + .into() + } } impl DrawableComponent for PushComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.visible { - let (state, progress) = - Self::get_progress(&self.progress); + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.visible { + let (state, progress) = + Self::get_progress(&self.progress); - let area = ui::centered_rect_absolute(30, 3, f.size()); + let area = ui::centered_rect_absolute(30, 3, f.size()); - f.render_widget(Clear, area); - f.render_widget( - Gauge::default() - .label(state.as_str()) - .block( - Block::default() - .title(Span::styled( - if self.modifier.force() { - strings::FORCE_PUSH_POPUP_MSG - } else { - strings::PUSH_POPUP_MSG - }, - self.theme.title(true), - )) - .borders(Borders::ALL) - .border_type(BorderType::Thick) - .border_style(self.theme.block(true)), - ) - .gauge_style(self.theme.push_gauge()) - .percent(u16::from(progress)), - area, - ); - self.input_cred.draw(f, rect)?; - } + f.render_widget(Clear, area); + f.render_widget( + Gauge::default() + .label(state.as_str()) + .block( + Block::default() + .title(Span::styled( + if self.modifier.force() { + strings::FORCE_PUSH_POPUP_MSG + } else { + strings::PUSH_POPUP_MSG + }, + self.theme.title(true), + )) + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .border_style(self.theme.block(true)), + ) + .gauge_style(self.theme.push_gauge()) + .percent(u16::from(progress)), + area, + ); + self.input_cred.draw(f, rect)?; + } - Ok(()) - } + Ok(()) + } } impl Component for PushComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - if !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + if !force_all { + out.clear(); + } - if self.input_cred.is_visible() { - return self.input_cred.commands(out, force_all); - } - out.push(CommandInfo::new( - strings::commands::close_msg(&self.key_config), - !self.pending, - self.visible, - )); - } + if self.input_cred.is_visible() { + return self.input_cred.commands(out, force_all); + } + out.push(CommandInfo::new( + strings::commands::close_msg(&self.key_config), + !self.pending, + self.visible, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if self.input_cred.is_visible() { - self.input_cred.event(ev)?; + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if self.input_cred.is_visible() { + self.input_cred.event(ev)?; - if self.input_cred.get_cred().is_complete() - || !self.input_cred.is_visible() - { - self.push_to_remote( - Some(self.input_cred.get_cred().clone()), - self.modifier.force(), - )?; - self.input_cred.hide(); - } - } else if e == self.key_config.exit_popup - && !self.pending - { - self.hide(); - } - } - return Ok(EventState::Consumed); - } - Ok(EventState::NotConsumed) - } + if self.input_cred.get_cred().is_complete() + || !self.input_cred.is_visible() + { + self.push_to_remote( + Some(self.input_cred.get_cred().clone()), + self.modifier.force(), + )?; + self.input_cred.hide(); + } + } else if e == self.key_config.exit_popup + && !self.pending + { + self.hide(); + } + } + return Ok(EventState::Consumed); + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/push_tags.rs b/src/components/push_tags.rs index 79924c08..5bf8aa71 100644 --- a/src/components/push_tags.rs +++ b/src/components/push_tags.rs @@ -1,263 +1,263 @@ use crate::{ - components::{ - cred::CredComponent, visibility_blocking, CommandBlocking, - CommandInfo, Component, DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings::{self}, - ui::{self, style::SharedTheme}, + components::{ + cred::CredComponent, visibility_blocking, CommandBlocking, + CommandInfo, Component, DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings::{self}, + ui::{self, style::SharedTheme}, }; use anyhow::Result; use asyncgit::{ - sync::{ - cred::{ - extract_username_password, need_username_password, - BasicAuthCredential, - }, - get_default_remote, AsyncProgress, PushTagsProgress, - }, - AsyncGitNotification, AsyncPushTags, PushTagsRequest, CWD, + sync::{ + cred::{ + extract_username_password, need_username_password, + BasicAuthCredential, + }, + get_default_remote, AsyncProgress, PushTagsProgress, + }, + AsyncGitNotification, AsyncPushTags, PushTagsRequest, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use tui::{ - backend::Backend, - layout::Rect, - text::Span, - widgets::{Block, BorderType, Borders, Clear, Gauge}, - Frame, + backend::Backend, + layout::Rect, + text::Span, + widgets::{Block, BorderType, Borders, Clear, Gauge}, + Frame, }; /// pub struct PushTagsComponent { - visible: bool, - git_push: AsyncPushTags, - progress: Option, - pending: bool, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - input_cred: CredComponent, + visible: bool, + git_push: AsyncPushTags, + progress: Option, + pending: bool, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + input_cred: CredComponent, } impl PushTagsComponent { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - pending: false, - visible: false, - git_push: AsyncPushTags::new(sender), - progress: None, - input_cred: CredComponent::new( - theme.clone(), - key_config.clone(), - ), - theme, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + pending: false, + visible: false, + git_push: AsyncPushTags::new(sender), + progress: None, + input_cred: CredComponent::new( + theme.clone(), + key_config.clone(), + ), + theme, + key_config, + } + } - /// - pub fn push_tags(&mut self) -> Result<()> { - self.show()?; - if need_username_password()? { - let cred = - extract_username_password().unwrap_or_else(|_| { - BasicAuthCredential::new(None, None) - }); - if cred.is_complete() { - self.push_to_remote(Some(cred)) - } else { - self.input_cred.set_cred(cred); - self.input_cred.show() - } - } else { - self.push_to_remote(None) - } - } + /// + pub fn push_tags(&mut self) -> Result<()> { + self.show()?; + if need_username_password()? { + let cred = + extract_username_password().unwrap_or_else(|_| { + BasicAuthCredential::new(None, None) + }); + if cred.is_complete() { + self.push_to_remote(Some(cred)) + } else { + self.input_cred.set_cred(cred); + self.input_cred.show() + } + } else { + self.push_to_remote(None) + } + } - fn push_to_remote( - &mut self, - cred: Option, - ) -> Result<()> { - self.pending = true; - self.progress = None; - self.git_push.request(PushTagsRequest { - remote: get_default_remote(CWD)?, - basic_credential: cred, - })?; - Ok(()) - } + fn push_to_remote( + &mut self, + cred: Option, + ) -> Result<()> { + self.pending = true; + self.progress = None; + self.git_push.request(PushTagsRequest { + remote: get_default_remote(CWD)?, + basic_credential: cred, + })?; + Ok(()) + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::PushTags = ev { - self.update()?; - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::PushTags = ev { + self.update()?; + } + } - Ok(()) - } + Ok(()) + } - /// - fn update(&mut self) -> Result<()> { - self.pending = self.git_push.is_pending()?; - self.progress = self.git_push.progress()?; + /// + fn update(&mut self) -> Result<()> { + self.pending = self.git_push.is_pending()?; + self.progress = self.git_push.progress()?; - if !self.pending { - if let Some(err) = self.git_push.last_result()? { - self.queue.push(InternalEvent::ShowErrorMsg( - format!("push tags failed:\n{}", err), - )); - } - self.hide(); - } + if !self.pending { + if let Some(err) = self.git_push.last_result()? { + self.queue.push(InternalEvent::ShowErrorMsg( + format!("push tags failed:\n{}", err), + )); + } + self.hide(); + } - Ok(()) - } + Ok(()) + } - /// - pub const fn any_work_pending(&self) -> bool { - self.pending - } + /// + pub const fn any_work_pending(&self) -> bool { + self.pending + } - /// - pub fn get_progress( - progress: &Option, - ) -> (String, u8) { - progress.as_ref().map_or( - (strings::PUSH_POPUP_PROGRESS_NONE.into(), 0), - |progress| { - ( - Self::progress_state_name(progress), - progress.progress().progress, - ) - }, - ) - } + /// + pub fn get_progress( + progress: &Option, + ) -> (String, u8) { + progress.as_ref().map_or( + (strings::PUSH_POPUP_PROGRESS_NONE.into(), 0), + |progress| { + ( + Self::progress_state_name(progress), + progress.progress().progress, + ) + }, + ) + } - fn progress_state_name(progress: &PushTagsProgress) -> String { - match progress { - PushTagsProgress::CheckRemote => { - strings::PUSH_TAGS_STATES_FETCHING - } - PushTagsProgress::Push { .. } => { - strings::PUSH_TAGS_STATES_PUSHING - } - PushTagsProgress::Done => strings::PUSH_TAGS_STATES_DONE, - } - .to_string() - } + fn progress_state_name(progress: &PushTagsProgress) -> String { + match progress { + PushTagsProgress::CheckRemote => { + strings::PUSH_TAGS_STATES_FETCHING + } + PushTagsProgress::Push { .. } => { + strings::PUSH_TAGS_STATES_PUSHING + } + PushTagsProgress::Done => strings::PUSH_TAGS_STATES_DONE, + } + .to_string() + } } impl DrawableComponent for PushTagsComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.visible { - let (state, progress) = - Self::get_progress(&self.progress); + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.visible { + let (state, progress) = + Self::get_progress(&self.progress); - let area = ui::centered_rect_absolute(30, 3, f.size()); + let area = ui::centered_rect_absolute(30, 3, f.size()); - f.render_widget(Clear, area); - f.render_widget( - Gauge::default() - .label(state.as_str()) - .block( - Block::default() - .title(Span::styled( - strings::PUSH_TAGS_POPUP_MSG, - self.theme.title(true), - )) - .borders(Borders::ALL) - .border_type(BorderType::Thick) - .border_style(self.theme.block(true)), - ) - .gauge_style(self.theme.push_gauge()) - .percent(u16::from(progress)), - area, - ); - self.input_cred.draw(f, rect)?; - } + f.render_widget(Clear, area); + f.render_widget( + Gauge::default() + .label(state.as_str()) + .block( + Block::default() + .title(Span::styled( + strings::PUSH_TAGS_POPUP_MSG, + self.theme.title(true), + )) + .borders(Borders::ALL) + .border_type(BorderType::Thick) + .border_style(self.theme.block(true)), + ) + .gauge_style(self.theme.push_gauge()) + .percent(u16::from(progress)), + area, + ); + self.input_cred.draw(f, rect)?; + } - Ok(()) - } + Ok(()) + } } impl Component for PushTagsComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - if !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + if !force_all { + out.clear(); + } - if self.input_cred.is_visible() { - return self.input_cred.commands(out, force_all); - } + if self.input_cred.is_visible() { + return self.input_cred.commands(out, force_all); + } - out.push(CommandInfo::new( - strings::commands::close_msg(&self.key_config), - !self.pending, - self.visible, - )); - } - visibility_blocking(self) - } + out.push(CommandInfo::new( + strings::commands::close_msg(&self.key_config), + !self.pending, + self.visible, + )); + } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if self.input_cred.is_visible() { - self.input_cred.event(ev)?; + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if self.input_cred.is_visible() { + self.input_cred.event(ev)?; - if self.input_cred.get_cred().is_complete() - || !self.input_cred.is_visible() - { - self.push_to_remote(Some( - self.input_cred.get_cred().clone(), - ))?; - self.input_cred.hide(); - } - } else if e == self.key_config.exit_popup - && !self.pending - { - self.hide(); - } - } - return Ok(EventState::Consumed); - } - Ok(EventState::NotConsumed) - } + if self.input_cred.get_cred().is_complete() + || !self.input_cred.is_visible() + { + self.push_to_remote(Some( + self.input_cred.get_cred().clone(), + ))?; + self.input_cred.hide(); + } + } else if e == self.key_config.exit_popup + && !self.pending + { + self.hide(); + } + } + return Ok(EventState::Consumed); + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/rename_branch.rs b/src/components/rename_branch.rs index 4e6991a1..4a7889d2 100644 --- a/src/components/rename_branch.rs +++ b/src/components/rename_branch.rs @@ -1,161 +1,161 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - keys::SharedKeyConfig, - queue::{InternalEvent, NeedsUpdate, Queue}, - strings, - ui::style::SharedTheme, + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{self}, - CWD, + sync::{self}, + CWD, }; use crossterm::event::Event; use tui::{backend::Backend, layout::Rect, Frame}; pub struct RenameBranchComponent { - input: TextInputComponent, - branch_ref: Option, - queue: Queue, - key_config: SharedKeyConfig, + input: TextInputComponent, + branch_ref: Option, + queue: Queue, + key_config: SharedKeyConfig, } impl DrawableComponent for RenameBranchComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - self.input.draw(f, rect)?; + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; - Ok(()) - } + Ok(()) + } } impl Component for RenameBranchComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - self.input.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + self.input.commands(out, force_all); - out.push(CommandInfo::new( - strings::commands::rename_branch_confirm_msg( - &self.key_config, - ), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::rename_branch_confirm_msg( + &self.key_config, + ), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { - if self.input.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if self.input.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if let Event::Key(e) = ev { - if e == self.key_config.enter { - self.rename_branch(); - } + if let Event::Key(e) = ev { + if e == self.key_config.enter { + self.rename_branch(); + } - return Ok(EventState::Consumed); - } - } - Ok(EventState::NotConsumed) - } + return Ok(EventState::Consumed); + } + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.input.is_visible() - } + fn is_visible(&self) -> bool { + self.input.is_visible() + } - fn hide(&mut self) { - self.input.hide(); - } + fn hide(&mut self) { + self.input.hide(); + } - fn show(&mut self) -> Result<()> { - self.input.show()?; + fn show(&mut self) -> Result<()> { + self.input.show()?; - Ok(()) - } + Ok(()) + } } impl RenameBranchComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue, - input: TextInputComponent::new( - theme, - key_config.clone(), - &strings::rename_branch_popup_title(&key_config), - &strings::rename_branch_popup_msg(&key_config), - true, - ), - branch_ref: None, - key_config, - } - } + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue, + input: TextInputComponent::new( + theme, + key_config.clone(), + &strings::rename_branch_popup_title(&key_config), + &strings::rename_branch_popup_msg(&key_config), + true, + ), + branch_ref: None, + key_config, + } + } - /// - pub fn open( - &mut self, - branch_ref: String, - cur_name: String, - ) -> Result<()> { - self.branch_ref = None; - self.branch_ref = Some(branch_ref); - self.input.set_text(cur_name); - self.show()?; + /// + pub fn open( + &mut self, + branch_ref: String, + cur_name: String, + ) -> Result<()> { + self.branch_ref = None; + self.branch_ref = Some(branch_ref); + self.input.set_text(cur_name); + self.show()?; - Ok(()) - } + Ok(()) + } - /// - pub fn rename_branch(&mut self) { - if let Some(br) = &self.branch_ref { - let res = sync::rename_branch( - CWD, - br, - self.input.get_text().as_str(), - ); + /// + pub fn rename_branch(&mut self) { + if let Some(br) = &self.branch_ref { + let res = sync::rename_branch( + CWD, + br, + self.input.get_text().as_str(), + ); - match res { - Ok(_) => { - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - self.hide(); - self.queue.push(InternalEvent::SelectBranch); - } - Err(e) => { - log::error!("create branch: {}", e,); - self.queue.push(InternalEvent::ShowErrorMsg( - format!("rename branch error:\n{}", e,), - )); - } - } - } else { - log::error!("create branch: No branch selected"); - self.queue.push(InternalEvent::ShowErrorMsg( - "rename branch error: No branch selected to rename" - .to_string(), - )); - } + match res { + Ok(_) => { + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); + self.hide(); + self.queue.push(InternalEvent::SelectBranch); + } + Err(e) => { + log::error!("create branch: {}", e,); + self.queue.push(InternalEvent::ShowErrorMsg( + format!("rename branch error:\n{}", e,), + )); + } + } + } else { + log::error!("create branch: No branch selected"); + self.queue.push(InternalEvent::ShowErrorMsg( + "rename branch error: No branch selected to rename" + .to_string(), + )); + } - self.input.clear(); - } + self.input.clear(); + } } diff --git a/src/components/reset.rs b/src/components/reset.rs index 3498f3bf..1b00ef0e 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -1,140 +1,140 @@ use crate::{ - components::{ - popup_paragraph, visibility_blocking, CommandBlocking, - CommandInfo, Component, DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, Queue}, - strings, ui, + components::{ + popup_paragraph, visibility_blocking, CommandBlocking, + CommandInfo, Component, DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, Queue}, + strings, ui, }; use anyhow::Result; use crossterm::event::Event; use std::borrow::Cow; use tui::{ - backend::Backend, layout::Rect, text::Text, widgets::Clear, Frame, + backend::Backend, layout::Rect, text::Text, widgets::Clear, Frame, }; use ui::style::SharedTheme; /// pub struct ResetComponent { - target: Option, - visible: bool, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, + target: Option, + visible: bool, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, } impl DrawableComponent for ResetComponent { - fn draw( - &self, - f: &mut Frame, - _rect: Rect, - ) -> Result<()> { - if self.visible { - let (title, msg) = self.get_text(); + fn draw( + &self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { + if self.visible { + let (title, msg) = self.get_text(); - let txt = Text::styled( - Cow::from(msg), - self.theme.text_danger(), - ); + let txt = Text::styled( + Cow::from(msg), + self.theme.text_danger(), + ); - let area = ui::centered_rect(50, 20, f.size()); - f.render_widget(Clear, area); - f.render_widget( - popup_paragraph(&title, txt, &self.theme, true), - area, - ); - } + let area = ui::centered_rect(50, 20, f.size()); + f.render_widget(Clear, area); + f.render_widget( + popup_paragraph(&title, txt, &self.theme, true), + area, + ); + } - Ok(()) - } + Ok(()) + } } impl Component for ResetComponent { - fn commands( - &self, - out: &mut Vec, - _force_all: bool, - ) -> CommandBlocking { - out.push(CommandInfo::new( - strings::commands::confirm_action(&self.key_config), - true, - self.visible, - )); - out.push(CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - self.visible, - )); + fn commands( + &self, + out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + out.push(CommandInfo::new( + strings::commands::confirm_action(&self.key_config), + true, + self.visible, + )); + out.push(CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + self.visible, + )); - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - } else if e == self.key_config.enter { - self.confirm(); - } + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + } else if e == self.key_config.enter { + self.confirm(); + } - return Ok(EventState::Consumed); - } - } + return Ok(EventState::Consumed); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } impl ResetComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - target: None, - visible: false, - queue, - theme, - key_config, - } - } - /// - pub fn open(&mut self, a: Action) -> Result<()> { - self.target = Some(a); - self.show()?; + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + target: None, + visible: false, + queue, + theme, + key_config, + } + } + /// + pub fn open(&mut self, a: Action) -> Result<()> { + self.target = Some(a); + self.show()?; - Ok(()) - } - /// - pub fn confirm(&mut self) { - if let Some(a) = self.target.take() { - self.queue.push(InternalEvent::ConfirmedAction(a)); - } + Ok(()) + } + /// + pub fn confirm(&mut self) { + if let Some(a) = self.target.take() { + self.queue.push(InternalEvent::ConfirmedAction(a)); + } - self.hide(); - } + self.hide(); + } - fn get_text(&self) -> (String, String) { - if let Some(ref a) = self.target { - return match a { + fn get_text(&self) -> (String, String) { + if let Some(ref a) = self.target { + return match a { Action::Reset(_) => ( strings::confirm_title_reset(), strings::confirm_msg_reset(), @@ -202,8 +202,8 @@ impl ResetComponent { strings::confirm_msg_abortmerge(), ), }; - } + } - (String::new(), String::new()) - } + (String::new(), String::new()) + } } diff --git a/src/components/revision_files.rs b/src/components/revision_files.rs index 7a3ba043..b06538cc 100644 --- a/src/components/revision_files.rs +++ b/src/components/revision_files.rs @@ -1,30 +1,30 @@ use super::{ - utils::scroll_vertical::VerticalScroll, CommandBlocking, - CommandInfo, Component, DrawableComponent, EventState, - SyntaxTextComponent, + utils::scroll_vertical::VerticalScroll, CommandBlocking, + CommandInfo, Component, DrawableComponent, EventState, + SyntaxTextComponent, }; use crate::{ - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings::{self, order}, - ui::{self, common_nav, style::SharedTheme}, - AsyncAppNotification, AsyncNotification, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings::{self, order}, + ui::{self, common_nav, style::SharedTheme}, + AsyncAppNotification, AsyncNotification, }; use anyhow::Result; use asyncgit::{ - sync::{self, CommitId, TreeFile}, - CWD, + sync::{self, CommitId, TreeFile}, + CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use filetreelist::{FileTree, FileTreeItem}; use std::{collections::BTreeSet, convert::From, path::Path}; use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - text::Span, - widgets::{Block, Borders}, - Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + text::Span, + widgets::{Block, Borders}, + Frame, }; const FOLDER_ICON_COLLAPSED: &str = "\u{25b8}"; //▸ @@ -32,306 +32,306 @@ const FOLDER_ICON_EXPANDED: &str = "\u{25be}"; //▾ const EMPTY_STR: &str = ""; enum Focus { - Tree, - File, + Tree, + File, } pub struct RevisionFilesComponent { - queue: Queue, - theme: SharedTheme, - //TODO: store TreeFiles in `tree` - files: Vec, - current_file: SyntaxTextComponent, - tree: FileTree, - scroll: VerticalScroll, - revision: Option, - focus: Focus, - key_config: SharedKeyConfig, + queue: Queue, + theme: SharedTheme, + //TODO: store TreeFiles in `tree` + files: Vec, + current_file: SyntaxTextComponent, + tree: FileTree, + scroll: VerticalScroll, + revision: Option, + focus: Focus, + key_config: SharedKeyConfig, } impl RevisionFilesComponent { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - tree: FileTree::default(), - scroll: VerticalScroll::new(), - current_file: SyntaxTextComponent::new( - sender, - key_config.clone(), - theme.clone(), - ), - theme, - files: Vec::new(), - revision: None, - focus: Focus::Tree, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + tree: FileTree::default(), + scroll: VerticalScroll::new(), + current_file: SyntaxTextComponent::new( + sender, + key_config.clone(), + theme.clone(), + ), + theme, + files: Vec::new(), + revision: None, + focus: Focus::Tree, + key_config, + } + } - /// - pub fn set_commit(&mut self, commit: CommitId) -> Result<()> { - let same_id = - self.revision.map(|c| c == commit).unwrap_or_default(); - if !same_id { - self.files = sync::tree_files(CWD, commit)?; - let filenames: Vec<&Path> = - self.files.iter().map(|f| f.path.as_path()).collect(); - self.tree = FileTree::new(&filenames, &BTreeSet::new())?; - self.tree.collapse_but_root(); - self.revision = Some(commit); - } + /// + pub fn set_commit(&mut self, commit: CommitId) -> Result<()> { + let same_id = + self.revision.map(|c| c == commit).unwrap_or_default(); + if !same_id { + self.files = sync::tree_files(CWD, commit)?; + let filenames: Vec<&Path> = + self.files.iter().map(|f| f.path.as_path()).collect(); + self.tree = FileTree::new(&filenames, &BTreeSet::new())?; + self.tree.collapse_but_root(); + self.revision = Some(commit); + } - Ok(()) - } + Ok(()) + } - /// - pub fn update(&mut self, ev: AsyncNotification) { - self.current_file.update(ev); - } + /// + pub fn update(&mut self, ev: AsyncNotification) { + self.current_file.update(ev); + } - /// - pub fn any_work_pending(&self) -> bool { - self.current_file.any_work_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.current_file.any_work_pending() + } - fn tree_item_to_span<'a>( - item: &'a FileTreeItem, - theme: &SharedTheme, - selected: bool, - ) -> Span<'a> { - let path = item.info().path_str(); - let indent = item.info().indent(); + fn tree_item_to_span<'a>( + item: &'a FileTreeItem, + theme: &SharedTheme, + selected: bool, + ) -> Span<'a> { + let path = item.info().path_str(); + let indent = item.info().indent(); - let indent_str = if indent == 0 { - String::from("") - } else { - format!("{:w$}", " ", w = (indent as usize) * 2) - }; + let indent_str = if indent == 0 { + String::from("") + } else { + format!("{:w$}", " ", w = (indent as usize) * 2) + }; - let is_path = item.kind().is_path(); - let path_arrow = if is_path { - if item.kind().is_path_collapsed() { - FOLDER_ICON_COLLAPSED - } else { - FOLDER_ICON_EXPANDED - } - } else { - EMPTY_STR - }; + let is_path = item.kind().is_path(); + let path_arrow = if is_path { + if item.kind().is_path_collapsed() { + FOLDER_ICON_COLLAPSED + } else { + FOLDER_ICON_EXPANDED + } + } else { + EMPTY_STR + }; - let path = format!("{}{}{}", indent_str, path_arrow, path); - Span::styled(path, theme.file_tree_item(is_path, selected)) - } + let path = format!("{}{}{}", indent_str, path_arrow, path); + Span::styled(path, theme.file_tree_item(is_path, selected)) + } - fn blame(&self) -> bool { - self.tree.selected_file().map_or(false, |file| { - self.queue.push(InternalEvent::BlameFile( - file.full_path_str() - .strip_prefix("./") - .unwrap_or_default() - .to_string(), - )); - true - }) - } + fn blame(&self) -> bool { + self.tree.selected_file().map_or(false, |file| { + self.queue.push(InternalEvent::BlameFile( + file.full_path_str() + .strip_prefix("./") + .unwrap_or_default() + .to_string(), + )); + true + }) + } - fn selection_changed(&mut self) { - //TODO: retrieve TreeFile from tree datastructure - if let Some(file) = self - .tree - .selected_file() - .map(|file| file.full_path_str().to_string()) - { - let path = Path::new(&file); - if let Some(item) = - self.files.iter().find(|f| f.path == path) - { - if let Ok(path) = path.strip_prefix("./") { - return self.current_file.load_file( - path.to_string_lossy().to_string(), - item, - ); - } - } - self.current_file.clear(); - } - } + fn selection_changed(&mut self) { + //TODO: retrieve TreeFile from tree datastructure + if let Some(file) = self + .tree + .selected_file() + .map(|file| file.full_path_str().to_string()) + { + let path = Path::new(&file); + if let Some(item) = + self.files.iter().find(|f| f.path == path) + { + if let Ok(path) = path.strip_prefix("./") { + return self.current_file.load_file( + path.to_string_lossy().to_string(), + item, + ); + } + } + self.current_file.clear(); + } + } - fn draw_tree(&self, f: &mut Frame, area: Rect) { - let tree_height = usize::from(area.height.saturating_sub(2)); + fn draw_tree(&self, f: &mut Frame, area: Rect) { + let tree_height = usize::from(area.height.saturating_sub(2)); - self.tree.visual_selection().map_or_else( - || { - self.scroll.reset(); - }, - |selection| { - self.scroll.update( - selection.index, - selection.count, - tree_height, - ); - }, - ); + self.tree.visual_selection().map_or_else( + || { + self.scroll.reset(); + }, + |selection| { + self.scroll.update( + selection.index, + selection.count, + tree_height, + ); + }, + ); - let items = self - .tree - .iterate(self.scroll.get_top(), tree_height) - .map(|(item, selected)| { - Self::tree_item_to_span(item, &self.theme, selected) - }); + let items = self + .tree + .iterate(self.scroll.get_top(), tree_height) + .map(|(item, selected)| { + Self::tree_item_to_span(item, &self.theme, selected) + }); - let is_tree_focused = matches!(self.focus, Focus::Tree); + let is_tree_focused = matches!(self.focus, Focus::Tree); - let title = format!( - "Files at [{}]", - self.revision - .map(|c| c.get_short_string()) - .unwrap_or_default() - ); - ui::draw_list_block( - f, - area, - Block::default() - .title(Span::styled( - title, - self.theme.title(is_tree_focused), - )) - .borders(Borders::ALL) - .border_style(self.theme.block(is_tree_focused)), - items, - ); + let title = format!( + "Files at [{}]", + self.revision + .map(|c| c.get_short_string()) + .unwrap_or_default() + ); + ui::draw_list_block( + f, + area, + Block::default() + .title(Span::styled( + title, + self.theme.title(is_tree_focused), + )) + .borders(Borders::ALL) + .border_style(self.theme.block(is_tree_focused)), + items, + ); - if is_tree_focused { - self.scroll.draw(f, area, &self.theme); - } - } + if is_tree_focused { + self.scroll.draw(f, area, &self.theme); + } + } } impl DrawableComponent for RevisionFilesComponent { - fn draw( - &self, - f: &mut Frame, - area: Rect, - ) -> Result<()> { - if self.is_visible() { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage(40), - Constraint::Percentage(60), - ] - .as_ref(), - ) - .split(area); + fn draw( + &self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { + if self.is_visible() { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(40), + Constraint::Percentage(60), + ] + .as_ref(), + ) + .split(area); - self.draw_tree(f, chunks[0]); + self.draw_tree(f, chunks[0]); - self.current_file.draw(f, chunks[1])?; - } - Ok(()) - } + self.current_file.draw(f, chunks[1])?; + } + Ok(()) + } } impl Component for RevisionFilesComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if matches!(self.focus, Focus::Tree) || force_all { - out.push( - CommandInfo::new( - strings::commands::blame_file(&self.key_config), - self.tree.selected_file().is_some(), - true, - ) - .order(order::NAV), - ); - tree_nav_cmds(&self.tree, &self.key_config, out); - } else { - self.current_file.commands(out, force_all); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if matches!(self.focus, Focus::Tree) || force_all { + out.push( + CommandInfo::new( + strings::commands::blame_file(&self.key_config), + self.tree.selected_file().is_some(), + true, + ) + .order(order::NAV), + ); + tree_nav_cmds(&self.tree, &self.key_config, out); + } else { + self.current_file.commands(out, force_all); + } - CommandBlocking::PassingOn - } + CommandBlocking::PassingOn + } - fn event( - &mut self, - event: crossterm::event::Event, - ) -> Result { - if let Event::Key(key) = event { - let is_tree_focused = matches!(self.focus, Focus::Tree); - if is_tree_focused - && tree_nav(&mut self.tree, &self.key_config, key) - { - self.selection_changed(); - return Ok(EventState::Consumed); - } else if key == self.key_config.blame { - if self.blame() { - self.hide(); - return Ok(EventState::Consumed); - } - } else if key == self.key_config.move_right { - if is_tree_focused { - self.focus = Focus::File; - self.current_file.focus(true); - self.focus(true); - return Ok(EventState::Consumed); - } - } else if key == self.key_config.move_left { - if !is_tree_focused { - self.focus = Focus::Tree; - self.current_file.focus(false); - self.focus(false); - return Ok(EventState::Consumed); - } - } else if !is_tree_focused { - return self.current_file.event(event); - } - } + fn event( + &mut self, + event: crossterm::event::Event, + ) -> Result { + if let Event::Key(key) = event { + let is_tree_focused = matches!(self.focus, Focus::Tree); + if is_tree_focused + && tree_nav(&mut self.tree, &self.key_config, key) + { + self.selection_changed(); + return Ok(EventState::Consumed); + } else if key == self.key_config.blame { + if self.blame() { + self.hide(); + return Ok(EventState::Consumed); + } + } else if key == self.key_config.move_right { + if is_tree_focused { + self.focus = Focus::File; + self.current_file.focus(true); + self.focus(true); + return Ok(EventState::Consumed); + } + } else if key == self.key_config.move_left { + if !is_tree_focused { + self.focus = Focus::Tree; + self.current_file.focus(false); + self.focus(false); + return Ok(EventState::Consumed); + } + } else if !is_tree_focused { + return self.current_file.event(event); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } } //TODO: reuse for other tree usages fn tree_nav_cmds( - tree: &FileTree, - key_config: &SharedKeyConfig, - out: &mut Vec, + tree: &FileTree, + key_config: &SharedKeyConfig, + out: &mut Vec, ) { - out.push( - CommandInfo::new( - strings::commands::navigate_tree(key_config), - !tree.is_empty(), - true, - ) - .order(order::NAV), - ); + out.push( + CommandInfo::new( + strings::commands::navigate_tree(key_config), + !tree.is_empty(), + true, + ) + .order(order::NAV), + ); } //TODO: reuse for other tree usages fn tree_nav( - tree: &mut FileTree, - key_config: &SharedKeyConfig, - key: crossterm::event::KeyEvent, + tree: &mut FileTree, + key_config: &SharedKeyConfig, + key: crossterm::event::KeyEvent, ) -> bool { - if let Some(common_nav) = common_nav(key, key_config) { - tree.move_selection(common_nav) - } else if key == key_config.tree_collapse_recursive { - tree.collapse_recursive(); - true - } else if key == key_config.tree_expand_recursive { - tree.expand_recursive(); - true - } else { - false - } + if let Some(common_nav) = common_nav(key, key_config) { + tree.move_selection(common_nav) + } else if key == key_config.tree_collapse_recursive { + tree.collapse_recursive(); + true + } else if key == key_config.tree_expand_recursive { + tree.expand_recursive(); + true + } else { + false + } } diff --git a/src/components/revision_files_popup.rs b/src/components/revision_files_popup.rs index dcf61e2d..210262ea 100644 --- a/src/components/revision_files_popup.rs +++ b/src/components/revision_files_popup.rs @@ -1,14 +1,14 @@ use super::{ - revision_files::RevisionFilesComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + revision_files::RevisionFilesComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - keys::SharedKeyConfig, - queue::Queue, - strings::{self}, - ui::style::SharedTheme, - AsyncAppNotification, AsyncNotification, + keys::SharedKeyConfig, + queue::Queue, + strings::{self}, + ui::style::SharedTheme, + AsyncAppNotification, AsyncNotification, }; use anyhow::Result; use asyncgit::sync::CommitId; @@ -17,116 +17,116 @@ use crossterm::event::Event; use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame}; pub struct RevisionFilesPopup { - visible: bool, - key_config: SharedKeyConfig, - files: RevisionFilesComponent, + visible: bool, + key_config: SharedKeyConfig, + files: RevisionFilesComponent, } impl RevisionFilesPopup { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - files: RevisionFilesComponent::new( - queue, - sender, - theme, - key_config.clone(), - ), - visible: false, - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + files: RevisionFilesComponent::new( + queue, + sender, + theme, + key_config.clone(), + ), + visible: false, + key_config, + } + } - /// - pub fn open(&mut self, commit: CommitId) -> Result<()> { - self.files.set_commit(commit)?; - self.show()?; + /// + pub fn open(&mut self, commit: CommitId) -> Result<()> { + self.files.set_commit(commit)?; + self.show()?; - Ok(()) - } + Ok(()) + } - /// - pub fn update(&mut self, ev: AsyncNotification) { - self.files.update(ev); - } + /// + pub fn update(&mut self, ev: AsyncNotification) { + self.files.update(ev); + } - /// - pub fn any_work_pending(&self) -> bool { - self.files.any_work_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.files.any_work_pending() + } } impl DrawableComponent for RevisionFilesPopup { - fn draw( - &self, - f: &mut Frame, - area: Rect, - ) -> Result<()> { - if self.is_visible() { - f.render_widget(Clear, area); + fn draw( + &self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { + if self.is_visible() { + f.render_widget(Clear, area); - self.files.draw(f, area)?; - } + self.files.draw(f, area)?; + } - Ok(()) - } + Ok(()) + } } impl Component for RevisionFilesPopup { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - out.push( - CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - ) - .order(1), - ); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + out.push( + CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + ) + .order(1), + ); - self.files.commands(out, force_all); - } + self.files.commands(out, force_all); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event( - &mut self, - event: crossterm::event::Event, - ) -> Result { - if self.is_visible() { - if let Event::Key(key) = &event { - if *key == self.key_config.exit_popup { - self.hide(); - } - } + fn event( + &mut self, + event: crossterm::event::Event, + ) -> Result { + if self.is_visible() { + if let Event::Key(key) = &event { + if *key == self.key_config.exit_popup { + self.hide(); + } + } - return self.files.event(event); - } + return self.files.event(event); + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } diff --git a/src/components/stashmsg.rs b/src/components/stashmsg.rs index d7710c74..e7b1e979 100644 --- a/src/components/stashmsg.rs +++ b/src/components/stashmsg.rs @@ -1,14 +1,14 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - keys::SharedKeyConfig, - queue::{InternalEvent, NeedsUpdate, Queue}, - strings, - tabs::StashingOptions, - ui::style::SharedTheme, + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings, + tabs::StashingOptions, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{sync, CWD}; @@ -16,133 +16,133 @@ use crossterm::event::Event; use tui::{backend::Backend, layout::Rect, Frame}; pub struct StashMsgComponent { - options: StashingOptions, - input: TextInputComponent, - queue: Queue, - key_config: SharedKeyConfig, + options: StashingOptions, + input: TextInputComponent, + queue: Queue, + key_config: SharedKeyConfig, } impl DrawableComponent for StashMsgComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - self.input.draw(f, rect)?; + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; - Ok(()) - } + Ok(()) + } } impl Component for StashMsgComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - self.input.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + self.input.commands(out, force_all); - out.push(CommandInfo::new( - strings::commands::stashing_confirm_msg( - &self.key_config, - ), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::stashing_confirm_msg( + &self.key_config, + ), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { - if self.input.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if self.input.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if let Event::Key(e) = ev { - if e == self.key_config.enter { - match sync::stash_save( - CWD, - if self.input.get_text().is_empty() { - None - } else { - Some(self.input.get_text().as_str()) - }, - self.options.stash_untracked, - self.options.keep_index, - ) { - Ok(_) => { - self.input.clear(); - self.hide(); + if let Event::Key(e) = ev { + if e == self.key_config.enter { + match sync::stash_save( + CWD, + if self.input.get_text().is_empty() { + None + } else { + Some(self.input.get_text().as_str()) + }, + self.options.stash_untracked, + self.options.keep_index, + ) { + Ok(_) => { + self.input.clear(); + self.hide(); - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - } - Err(e) => { - self.hide(); - log::error!( - "e: {} (options: {:?})", - e, - self.options - ); - self.queue.push( + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); + } + Err(e) => { + self.hide(); + log::error!( + "e: {} (options: {:?})", + e, + self.options + ); + self.queue.push( InternalEvent::ShowErrorMsg(format!( "stash error:\n{}\noptions:\n{:?}", e, self.options )), ); - } - } - } + } + } + } - // stop key event propagation - return Ok(EventState::Consumed); - } - } - Ok(EventState::NotConsumed) - } + // stop key event propagation + return Ok(EventState::Consumed); + } + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.input.is_visible() - } + fn is_visible(&self) -> bool { + self.input.is_visible() + } - fn hide(&mut self) { - self.input.hide(); - } + fn hide(&mut self) { + self.input.hide(); + } - fn show(&mut self) -> Result<()> { - self.input.show()?; + fn show(&mut self) -> Result<()> { + self.input.show()?; - Ok(()) - } + Ok(()) + } } impl StashMsgComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - options: StashingOptions::default(), - queue, - input: TextInputComponent::new( - theme, - key_config.clone(), - &strings::stash_popup_title(&key_config), - &strings::stash_popup_msg(&key_config), - true, - ), - key_config, - } - } + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + options: StashingOptions::default(), + queue, + input: TextInputComponent::new( + theme, + key_config.clone(), + &strings::stash_popup_title(&key_config), + &strings::stash_popup_msg(&key_config), + true, + ), + key_config, + } + } - /// - pub fn options(&mut self, options: StashingOptions) { - self.options = options; - } + /// + pub fn options(&mut self, options: StashingOptions) { + self.options = options; + } } diff --git a/src/components/syntax_text.rs b/src/components/syntax_text.rs index 73de6d7b..26d4eba2 100644 --- a/src/components/syntax_text.rs +++ b/src/components/syntax_text.rs @@ -1,21 +1,21 @@ use super::{ - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - keys::SharedKeyConfig, - strings, - ui::{ - self, common_nav, style::SharedTheme, AsyncSyntaxJob, - ParagraphState, ScrollPos, StatefulParagraph, - }, - AsyncAppNotification, AsyncNotification, + keys::SharedKeyConfig, + strings, + ui::{ + self, common_nav, style::SharedTheme, AsyncSyntaxJob, + ParagraphState, ScrollPos, StatefulParagraph, + }, + AsyncAppNotification, AsyncNotification, }; use anyhow::Result; use asyncgit::{ - asyncjob::AsyncSingleJob, - sync::{self, TreeFile}, - CWD, + asyncjob::AsyncSingleJob, + sync::{self, TreeFile}, + CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; @@ -23,251 +23,251 @@ use filetreelist::MoveSelection; use itertools::Either; use std::{cell::Cell, convert::From, path::Path}; use tui::{ - backend::Backend, - layout::Rect, - text::Text, - widgets::{Block, Borders, Wrap}, - Frame, + backend::Backend, + layout::Rect, + text::Text, + widgets::{Block, Borders, Wrap}, + Frame, }; pub struct SyntaxTextComponent { - current_file: Option<(String, Either)>, - async_highlighting: - AsyncSingleJob, - key_config: SharedKeyConfig, - paragraph_state: Cell, - focused: bool, - theme: SharedTheme, + current_file: Option<(String, Either)>, + async_highlighting: + AsyncSingleJob, + key_config: SharedKeyConfig, + paragraph_state: Cell, + focused: bool, + theme: SharedTheme, } impl SyntaxTextComponent { - /// - pub fn new( - sender: &Sender, - key_config: SharedKeyConfig, - theme: SharedTheme, - ) -> Self { - Self { - async_highlighting: AsyncSingleJob::new( - sender.clone(), - AsyncAppNotification::SyntaxHighlighting, - ), - current_file: None, - paragraph_state: Cell::new(ParagraphState::default()), - focused: false, - key_config, - theme, - } - } + /// + pub fn new( + sender: &Sender, + key_config: SharedKeyConfig, + theme: SharedTheme, + ) -> Self { + Self { + async_highlighting: AsyncSingleJob::new( + sender.clone(), + AsyncAppNotification::SyntaxHighlighting, + ), + current_file: None, + paragraph_state: Cell::new(ParagraphState::default()), + focused: false, + key_config, + theme, + } + } - /// - pub fn update(&mut self, ev: AsyncNotification) { - if matches!( - ev, - AsyncNotification::App( - AsyncAppNotification::SyntaxHighlighting - ) - ) { - if let Some(job) = self.async_highlighting.take_last() { - if let Some((path, content)) = - self.current_file.as_mut() - { - if let Some(syntax) = job.result() { - if syntax.path() == Path::new(path) { - *content = Either::Left(syntax); - } - } - } - } - } - } + /// + pub fn update(&mut self, ev: AsyncNotification) { + if matches!( + ev, + AsyncNotification::App( + AsyncAppNotification::SyntaxHighlighting + ) + ) { + if let Some(job) = self.async_highlighting.take_last() { + if let Some((path, content)) = + self.current_file.as_mut() + { + if let Some(syntax) = job.result() { + if syntax.path() == Path::new(path) { + *content = Either::Left(syntax); + } + } + } + } + } + } - /// - pub fn any_work_pending(&self) -> bool { - self.async_highlighting.is_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.async_highlighting.is_pending() + } - /// - pub fn clear(&mut self) { - self.current_file = None; - } + /// + pub fn clear(&mut self) { + self.current_file = None; + } - /// - pub fn load_file(&mut self, path: String, item: &TreeFile) { - let already_loaded = self - .current_file - .as_ref() - .map(|(current_file, _)| current_file == &path) - .unwrap_or_default(); + /// + pub fn load_file(&mut self, path: String, item: &TreeFile) { + let already_loaded = self + .current_file + .as_ref() + .map(|(current_file, _)| current_file == &path) + .unwrap_or_default(); - if !already_loaded { - //TODO: fetch file content async aswell - match sync::tree_file_content(CWD, item) { - Ok(content) => { - self.async_highlighting.spawn( - AsyncSyntaxJob::new( - content.clone(), - path.clone(), - ), - ); + if !already_loaded { + //TODO: fetch file content async aswell + match sync::tree_file_content(CWD, item) { + Ok(content) => { + self.async_highlighting.spawn( + AsyncSyntaxJob::new( + content.clone(), + path.clone(), + ), + ); - self.current_file = - Some((path, Either::Right(content))); - } - Err(e) => { - self.current_file = Some(( - path, - Either::Right(format!( - "error loading file: {}", - e - )), - )); - } - } - } - } + self.current_file = + Some((path, Either::Right(content))); + } + Err(e) => { + self.current_file = Some(( + path, + Either::Right(format!( + "error loading file: {}", + e + )), + )); + } + } + } + } - fn scroll(&self, nav: MoveSelection) -> bool { - let state = self.paragraph_state.get(); + fn scroll(&self, nav: MoveSelection) -> bool { + let state = self.paragraph_state.get(); - let new_scroll_pos = match nav { - MoveSelection::Down => state.scroll().y.saturating_add(1), - MoveSelection::Up => state.scroll().y.saturating_sub(1), - MoveSelection::Top => 0, - MoveSelection::End => state - .lines() - .saturating_sub(state.height().saturating_sub(2)), - MoveSelection::PageUp => state - .scroll() - .y - .saturating_sub(state.height().saturating_sub(2)), - MoveSelection::PageDown => state - .scroll() - .y - .saturating_add(state.height().saturating_sub(2)), - _ => state.scroll().y, - }; + let new_scroll_pos = match nav { + MoveSelection::Down => state.scroll().y.saturating_add(1), + MoveSelection::Up => state.scroll().y.saturating_sub(1), + MoveSelection::Top => 0, + MoveSelection::End => state + .lines() + .saturating_sub(state.height().saturating_sub(2)), + MoveSelection::PageUp => state + .scroll() + .y + .saturating_sub(state.height().saturating_sub(2)), + MoveSelection::PageDown => state + .scroll() + .y + .saturating_add(state.height().saturating_sub(2)), + _ => state.scroll().y, + }; - self.set_scroll(new_scroll_pos) - } + self.set_scroll(new_scroll_pos) + } - fn set_scroll(&self, pos: u16) -> bool { - let mut state = self.paragraph_state.get(); + fn set_scroll(&self, pos: u16) -> bool { + let mut state = self.paragraph_state.get(); - let new_scroll_pos = pos.min( - state - .lines() - .saturating_sub(state.height().saturating_sub(2)), - ); + let new_scroll_pos = pos.min( + state + .lines() + .saturating_sub(state.height().saturating_sub(2)), + ); - if new_scroll_pos == state.scroll().y { - return false; - } + if new_scroll_pos == state.scroll().y { + return false; + } - state.set_scroll(ScrollPos { - x: 0, - y: new_scroll_pos, - }); - self.paragraph_state.set(state); + state.set_scroll(ScrollPos { + x: 0, + y: new_scroll_pos, + }); + self.paragraph_state.set(state); - true - } + true + } } impl DrawableComponent for SyntaxTextComponent { - fn draw( - &self, - f: &mut Frame, - area: Rect, - ) -> Result<()> { - let text = self.current_file.as_ref().map_or_else( - || Text::from(""), - |(_, content)| match content { - Either::Left(syn) => syn.into(), - Either::Right(s) => Text::from(s.as_str()), - }, - ); + fn draw( + &self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { + let text = self.current_file.as_ref().map_or_else( + || Text::from(""), + |(_, content)| match content { + Either::Left(syn) => syn.into(), + Either::Right(s) => Text::from(s.as_str()), + }, + ); - let content = StatefulParagraph::new(text) - .wrap(Wrap { trim: false }) - .block( - Block::default() - .title( - self.current_file - .as_ref() - .map(|(name, _)| name.clone()) - .unwrap_or_default(), - ) - .borders(Borders::ALL) - .border_style(self.theme.title(self.focused())), - ); + let content = StatefulParagraph::new(text) + .wrap(Wrap { trim: false }) + .block( + Block::default() + .title( + self.current_file + .as_ref() + .map(|(name, _)| name.clone()) + .unwrap_or_default(), + ) + .borders(Borders::ALL) + .border_style(self.theme.title(self.focused())), + ); - let mut state = self.paragraph_state.get(); + let mut state = self.paragraph_state.get(); - f.render_stateful_widget(content, area, &mut state); + f.render_stateful_widget(content, area, &mut state); - self.paragraph_state.set(state); + self.paragraph_state.set(state); - self.set_scroll(state.scroll().y); + self.set_scroll(state.scroll().y); - if self.focused() { - ui::draw_scrollbar( - f, - area, - &self.theme, - usize::from(state.lines().saturating_sub( - state.height().saturating_sub(2), - )), - usize::from(state.scroll().y), - ); - } + if self.focused() { + ui::draw_scrollbar( + f, + area, + &self.theme, + usize::from(state.lines().saturating_sub( + state.height().saturating_sub(2), + )), + usize::from(state.scroll().y), + ); + } - Ok(()) - } + Ok(()) + } } impl Component for SyntaxTextComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.focused() || force_all { - out.push( - CommandInfo::new( - strings::commands::scroll(&self.key_config), - true, - true, - ) - .order(strings::order::NAV), - ); - } - CommandBlocking::PassingOn - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.focused() || force_all { + out.push( + CommandInfo::new( + strings::commands::scroll(&self.key_config), + true, + true, + ) + .order(strings::order::NAV), + ); + } + CommandBlocking::PassingOn + } - fn event( - &mut self, - event: crossterm::event::Event, - ) -> Result { - if let Event::Key(key) = event { - if let Some(nav) = common_nav(key, &self.key_config) { - return Ok(self - .scroll(nav) - .then(|| EventState::Consumed) - .unwrap_or(EventState::NotConsumed)); - } - } + fn event( + &mut self, + event: crossterm::event::Event, + ) -> Result { + if let Event::Key(key) = event { + if let Some(nav) = common_nav(key, &self.key_config) { + return Ok(self + .scroll(nav) + .then(|| EventState::Consumed) + .unwrap_or(EventState::NotConsumed)); + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - /// - fn focused(&self) -> bool { - self.focused - } + /// + fn focused(&self) -> bool { + self.focused + } - /// focus/unfocus this component depending on param - fn focus(&mut self, focus: bool) { - self.focused = focus; - } + /// focus/unfocus this component depending on param + fn focus(&mut self, focus: bool) { + self.focused = focus; + } } diff --git a/src/components/tag_commit.rs b/src/components/tag_commit.rs index d06263f0..9647a5f3 100644 --- a/src/components/tag_commit.rs +++ b/src/components/tag_commit.rs @@ -1,143 +1,143 @@ use super::{ - textinput::TextInputComponent, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, + textinput::TextInputComponent, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, }; use crate::{ - keys::SharedKeyConfig, - queue::{InternalEvent, NeedsUpdate, Queue}, - strings, - ui::style::SharedTheme, + keys::SharedKeyConfig, + queue::{InternalEvent, NeedsUpdate, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{self, CommitId}, - CWD, + sync::{self, CommitId}, + CWD, }; use crossterm::event::Event; use tui::{backend::Backend, layout::Rect, Frame}; pub struct TagCommitComponent { - input: TextInputComponent, - commit_id: Option, - queue: Queue, - key_config: SharedKeyConfig, + input: TextInputComponent, + commit_id: Option, + queue: Queue, + key_config: SharedKeyConfig, } impl DrawableComponent for TagCommitComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - self.input.draw(f, rect)?; + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + self.input.draw(f, rect)?; - Ok(()) - } + Ok(()) + } } impl Component for TagCommitComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.is_visible() || force_all { - self.input.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.is_visible() || force_all { + self.input.commands(out, force_all); - out.push(CommandInfo::new( - strings::commands::tag_commit_confirm_msg( - &self.key_config, - ), - true, - true, - )); - } + out.push(CommandInfo::new( + strings::commands::tag_commit_confirm_msg( + &self.key_config, + ), + true, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.is_visible() { - if self.input.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.is_visible() { + if self.input.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if let Event::Key(e) = ev { - if e == self.key_config.enter { - self.tag(); - } + if let Event::Key(e) = ev { + if e == self.key_config.enter { + self.tag(); + } - return Ok(EventState::Consumed); - } - } - Ok(EventState::NotConsumed) - } + return Ok(EventState::Consumed); + } + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.input.is_visible() - } + fn is_visible(&self) -> bool { + self.input.is_visible() + } - fn hide(&mut self) { - self.input.hide(); - } + fn hide(&mut self) { + self.input.hide(); + } - fn show(&mut self) -> Result<()> { - self.input.show()?; + fn show(&mut self) -> Result<()> { + self.input.show()?; - Ok(()) - } + Ok(()) + } } impl TagCommitComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue, - input: TextInputComponent::new( - theme, - key_config.clone(), - &strings::tag_commit_popup_title(&key_config), - &strings::tag_commit_popup_msg(&key_config), - true, - ), - commit_id: None, - key_config, - } - } + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue, + input: TextInputComponent::new( + theme, + key_config.clone(), + &strings::tag_commit_popup_title(&key_config), + &strings::tag_commit_popup_msg(&key_config), + true, + ), + commit_id: None, + key_config, + } + } - /// - pub fn open(&mut self, id: CommitId) -> Result<()> { - self.commit_id = Some(id); - self.show()?; + /// + pub fn open(&mut self, id: CommitId) -> Result<()> { + self.commit_id = Some(id); + self.show()?; - Ok(()) - } + Ok(()) + } - /// - pub fn tag(&mut self) { - if let Some(commit_id) = self.commit_id { - match sync::tag(CWD, &commit_id, self.input.get_text()) { - Ok(_) => { - self.input.clear(); - self.hide(); + /// + pub fn tag(&mut self) { + if let Some(commit_id) = self.commit_id { + match sync::tag(CWD, &commit_id, self.input.get_text()) { + Ok(_) => { + self.input.clear(); + self.hide(); - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - } - Err(e) => { - self.hide(); - log::error!("e: {}", e,); - self.queue.push(InternalEvent::ShowErrorMsg( - format!("tag error:\n{}", e,), - )); - } - } - } - } + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); + } + Err(e) => { + self.hide(); + log::error!("e: {}", e,); + self.queue.push(InternalEvent::ShowErrorMsg( + format!("tag error:\n{}", e,), + )); + } + } + } + } } diff --git a/src/components/taglist.rs b/src/components/taglist.rs index 8df70364..67818b18 100644 --- a/src/components/taglist.rs +++ b/src/components/taglist.rs @@ -1,435 +1,435 @@ use super::{ - utils, visibility_blocking, CommandBlocking, CommandInfo, - Component, DrawableComponent, EventState, + utils, visibility_blocking, CommandBlocking, CommandInfo, + Component, DrawableComponent, EventState, }; use crate::{ - components::ScrollType, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, Queue}, - strings, - ui::{self, Size}, - AsyncAppNotification, AsyncNotification, + components::ScrollType, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, Queue}, + strings, + ui::{self, Size}, + AsyncAppNotification, AsyncNotification, }; use anyhow::Result; use asyncgit::{ - asyncjob::AsyncSingleJob, - remote_tags::AsyncRemoteTagsJob, - sync::cred::{ - extract_username_password, need_username_password, - BasicAuthCredential, - }, - sync::{get_tags_with_metadata, TagWithMetadata}, - AsyncGitNotification, CWD, + asyncjob::AsyncSingleJob, + remote_tags::AsyncRemoteTagsJob, + sync::cred::{ + extract_username_password, need_username_password, + BasicAuthCredential, + }, + sync::{get_tags_with_metadata, TagWithMetadata}, + AsyncGitNotification, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use std::convert::TryInto; use tui::{ - backend::Backend, - layout::{Constraint, Margin, Rect}, - text::Span, - widgets::{ - Block, BorderType, Borders, Cell, Clear, Row, Table, - TableState, - }, - Frame, + backend::Backend, + layout::{Constraint, Margin, Rect}, + text::Span, + widgets::{ + Block, BorderType, Borders, Cell, Clear, Row, Table, + TableState, + }, + Frame, }; use ui::style::SharedTheme; /// pub struct TagListComponent { - theme: SharedTheme, - queue: Queue, - tags: Option>, - visible: bool, - table_state: std::cell::Cell, - current_height: std::cell::Cell, - missing_remote_tags: Option>, - basic_credential: Option, - async_remote_tags: - AsyncSingleJob, - key_config: SharedKeyConfig, + theme: SharedTheme, + queue: Queue, + tags: Option>, + visible: bool, + table_state: std::cell::Cell, + current_height: std::cell::Cell, + missing_remote_tags: Option>, + basic_credential: Option, + async_remote_tags: + AsyncSingleJob, + key_config: SharedKeyConfig, } impl DrawableComponent for TagListComponent { - fn draw( - &self, - f: &mut Frame, - rect: Rect, - ) -> Result<()> { - if self.visible { - const PERCENT_SIZE: Size = Size::new(80, 50); - const MIN_SIZE: Size = Size::new(60, 20); + fn draw( + &self, + f: &mut Frame, + rect: Rect, + ) -> Result<()> { + if self.visible { + const PERCENT_SIZE: Size = Size::new(80, 50); + const MIN_SIZE: Size = Size::new(60, 20); - let area = ui::centered_rect( - PERCENT_SIZE.width, - PERCENT_SIZE.height, - f.size(), - ); - let area = - ui::rect_inside(MIN_SIZE, f.size().into(), area); - let area = area.intersection(rect); + let area = ui::centered_rect( + PERCENT_SIZE.width, + PERCENT_SIZE.height, + f.size(), + ); + let area = + ui::rect_inside(MIN_SIZE, f.size().into(), area); + let area = area.intersection(rect); - let tag_name_width = - self.tags.as_ref().map_or(0, |tags| { - tags.iter() - .fold(0, |acc, tag| acc.max(tag.name.len())) - }); + let tag_name_width = + self.tags.as_ref().map_or(0, |tags| { + tags.iter() + .fold(0, |acc, tag| acc.max(tag.name.len())) + }); - let constraints = [ - // symbol if tag is not yet on remote and can be pushed - Constraint::Length(1), - // tag name - Constraint::Length(tag_name_width.try_into()?), - // commit date - Constraint::Length(10), - // author width - Constraint::Length(19), - // commit id - Constraint::Min(0), - ]; + let constraints = [ + // symbol if tag is not yet on remote and can be pushed + Constraint::Length(1), + // tag name + Constraint::Length(tag_name_width.try_into()?), + // commit date + Constraint::Length(10), + // author width + Constraint::Length(19), + // commit id + Constraint::Min(0), + ]; - let rows = self.get_rows(); - let number_of_rows = rows.len(); + let rows = self.get_rows(); + let number_of_rows = rows.len(); - let table = Table::new(rows) - .widths(&constraints) - .column_spacing(1) - .highlight_style(self.theme.text(true, true)) - .block( - Block::default() - .borders(Borders::ALL) - .title(Span::styled( - strings::title_tags(), - self.theme.title(true), - )) - .border_style(self.theme.block(true)) - .border_type(BorderType::Thick), - ); + let table = Table::new(rows) + .widths(&constraints) + .column_spacing(1) + .highlight_style(self.theme.text(true, true)) + .block( + Block::default() + .borders(Borders::ALL) + .title(Span::styled( + strings::title_tags(), + self.theme.title(true), + )) + .border_style(self.theme.block(true)) + .border_type(BorderType::Thick), + ); - let mut table_state = self.table_state.take(); + let mut table_state = self.table_state.take(); - f.render_widget(Clear, area); - f.render_stateful_widget(table, area, &mut table_state); + f.render_widget(Clear, area); + f.render_stateful_widget(table, area, &mut table_state); - let area = area.inner(&Margin { - vertical: 1, - horizontal: 0, - }); + let area = area.inner(&Margin { + vertical: 1, + horizontal: 0, + }); - ui::draw_scrollbar( - f, - area, - &self.theme, - number_of_rows, - table_state.selected().unwrap_or(0), - ); + ui::draw_scrollbar( + f, + area, + &self.theme, + number_of_rows, + table_state.selected().unwrap_or(0), + ); - self.table_state.set(table_state); - self.current_height.set(area.height.into()); - } + self.table_state.set(table_state); + self.current_height.set(area.height.into()); + } - Ok(()) - } + Ok(()) + } } impl Component for TagListComponent { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - if !force_all { - out.clear(); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + if !force_all { + out.clear(); + } - out.push(CommandInfo::new( - strings::commands::scroll(&self.key_config), - true, - true, - )); + out.push(CommandInfo::new( + strings::commands::scroll(&self.key_config), + true, + true, + )); - out.push(CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - true, - )); + out.push(CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + true, + )); - out.push(CommandInfo::new( - strings::commands::delete_tag_popup(&self.key_config), - self.valid_selection(), - true, - )); - out.push(CommandInfo::new( - strings::commands::select_tag(&self.key_config), - self.valid_selection(), - true, - )); - out.push(CommandInfo::new( - strings::commands::push_tags(&self.key_config), - true, - true, - )); - } - visibility_blocking(self) - } + out.push(CommandInfo::new( + strings::commands::delete_tag_popup(&self.key_config), + self.valid_selection(), + true, + )); + out.push(CommandInfo::new( + strings::commands::select_tag(&self.key_config), + self.valid_selection(), + true, + )); + out.push(CommandInfo::new( + strings::commands::push_tags(&self.key_config), + true, + true, + )); + } + visibility_blocking(self) + } - fn event(&mut self, event: Event) -> Result { - if self.visible { - if let Event::Key(key) = event { - if key == self.key_config.exit_popup { - self.hide(); - } else if key == self.key_config.move_up { - self.move_selection(ScrollType::Up); - } else if key == self.key_config.move_down { - self.move_selection(ScrollType::Down); - } else if key == self.key_config.shift_up - || key == self.key_config.home - { - self.move_selection(ScrollType::Home); - } else if key == self.key_config.shift_down - || key == self.key_config.end - { - self.move_selection(ScrollType::End); - } else if key == self.key_config.page_down { - self.move_selection(ScrollType::PageDown); - } else if key == self.key_config.page_up { - self.move_selection(ScrollType::PageUp); - } else if key == self.key_config.delete_tag { - return self.selected_tag().map_or( - Ok(EventState::NotConsumed), - |tag| { - self.queue.push( - InternalEvent::ConfirmAction( - Action::DeleteTag( - tag.name.clone(), - ), - ), - ); - Ok(EventState::Consumed) - }, - ); - } else if key == self.key_config.select_tag { - return self.selected_tag().map_or( - Ok(EventState::NotConsumed), - |tag| { - self.queue.push( - InternalEvent::SelectCommitInRevlog( - tag.commit_id, - ), - ); - Ok(EventState::Consumed) - }, - ); - } else if key == self.key_config.push { - self.queue.push(InternalEvent::PushTags); - } - } + fn event(&mut self, event: Event) -> Result { + if self.visible { + if let Event::Key(key) = event { + if key == self.key_config.exit_popup { + self.hide(); + } else if key == self.key_config.move_up { + self.move_selection(ScrollType::Up); + } else if key == self.key_config.move_down { + self.move_selection(ScrollType::Down); + } else if key == self.key_config.shift_up + || key == self.key_config.home + { + self.move_selection(ScrollType::Home); + } else if key == self.key_config.shift_down + || key == self.key_config.end + { + self.move_selection(ScrollType::End); + } else if key == self.key_config.page_down { + self.move_selection(ScrollType::PageDown); + } else if key == self.key_config.page_up { + self.move_selection(ScrollType::PageUp); + } else if key == self.key_config.delete_tag { + return self.selected_tag().map_or( + Ok(EventState::NotConsumed), + |tag| { + self.queue.push( + InternalEvent::ConfirmAction( + Action::DeleteTag( + tag.name.clone(), + ), + ), + ); + Ok(EventState::Consumed) + }, + ); + } else if key == self.key_config.select_tag { + return self.selected_tag().map_or( + Ok(EventState::NotConsumed), + |tag| { + self.queue.push( + InternalEvent::SelectCommitInRevlog( + tag.commit_id, + ), + ); + Ok(EventState::Consumed) + }, + ); + } else if key == self.key_config.push { + self.queue.push(InternalEvent::PushTags); + } + } - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - } - } + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + } + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } impl TagListComponent { - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - theme, - queue: queue.clone(), - tags: None, - visible: false, - table_state: std::cell::Cell::new(TableState::default()), - current_height: std::cell::Cell::new(0), - basic_credential: None, - missing_remote_tags: None, - async_remote_tags: AsyncSingleJob::new( - sender.clone(), - AsyncAppNotification::RemoteTags, - ), - key_config, - } - } + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + theme, + queue: queue.clone(), + tags: None, + visible: false, + table_state: std::cell::Cell::new(TableState::default()), + current_height: std::cell::Cell::new(0), + basic_credential: None, + missing_remote_tags: None, + async_remote_tags: AsyncSingleJob::new( + sender.clone(), + AsyncAppNotification::RemoteTags, + ), + key_config, + } + } - /// - pub fn open(&mut self) -> Result<()> { - self.table_state.get_mut().select(Some(0)); - self.show()?; + /// + pub fn open(&mut self) -> Result<()> { + self.table_state.get_mut().select(Some(0)); + self.show()?; - let basic_credential = if need_username_password()? { - let credential = extract_username_password()?; + let basic_credential = if need_username_password()? { + let credential = extract_username_password()?; - if credential.is_complete() { - Some(credential) - } else { - None - } - } else { - None - }; + if credential.is_complete() { + Some(credential) + } else { + None + } + } else { + None + }; - self.basic_credential = basic_credential; + self.basic_credential = basic_credential; - self.update_tags()?; - self.update_missing_remote_tags(); + self.update_tags()?; + self.update_missing_remote_tags(); - Ok(()) - } + Ok(()) + } - /// - pub fn update(&mut self, ev: AsyncNotification) { - if matches!( - ev, - AsyncNotification::App(AsyncAppNotification::RemoteTags) - ) { - if let Some(job) = self.async_remote_tags.take_last() { - if let Some(Ok(missing_remote_tags)) = job.result() { - self.missing_remote_tags = - Some(missing_remote_tags); - } - } - } else if matches!( - ev, - AsyncNotification::Git(AsyncGitNotification::PushTags) - ) { - self.update_missing_remote_tags(); - } - } + /// + pub fn update(&mut self, ev: AsyncNotification) { + if matches!( + ev, + AsyncNotification::App(AsyncAppNotification::RemoteTags) + ) { + if let Some(job) = self.async_remote_tags.take_last() { + if let Some(Ok(missing_remote_tags)) = job.result() { + self.missing_remote_tags = + Some(missing_remote_tags); + } + } + } else if matches!( + ev, + AsyncNotification::Git(AsyncGitNotification::PushTags) + ) { + self.update_missing_remote_tags(); + } + } - /// - pub fn any_work_pending(&self) -> bool { - self.async_remote_tags.is_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.async_remote_tags.is_pending() + } - /// fetch list of tags - pub fn update_tags(&mut self) -> Result<()> { - let tags = get_tags_with_metadata(CWD)?; + /// fetch list of tags + pub fn update_tags(&mut self) -> Result<()> { + let tags = get_tags_with_metadata(CWD)?; - self.tags = Some(tags); + self.tags = Some(tags); - Ok(()) - } + Ok(()) + } - pub fn update_missing_remote_tags(&mut self) { - self.async_remote_tags.spawn(AsyncRemoteTagsJob::new( - self.basic_credential.clone(), - )); - } + pub fn update_missing_remote_tags(&mut self) { + self.async_remote_tags.spawn(AsyncRemoteTagsJob::new( + self.basic_credential.clone(), + )); + } - /// - fn move_selection(&mut self, scroll_type: ScrollType) -> bool { - let mut table_state = self.table_state.take(); + /// + fn move_selection(&mut self, scroll_type: ScrollType) -> bool { + let mut table_state = self.table_state.take(); - let old_selection = table_state.selected().unwrap_or(0); - let max_selection = - self.tags.as_ref().map_or(0, |tags| tags.len() - 1); + let old_selection = table_state.selected().unwrap_or(0); + let max_selection = + self.tags.as_ref().map_or(0, |tags| tags.len() - 1); - let new_selection = match scroll_type { - ScrollType::Up => old_selection.saturating_sub(1), - ScrollType::Down => { - old_selection.saturating_add(1).min(max_selection) - } - ScrollType::Home => 0, - ScrollType::End => max_selection, - ScrollType::PageUp => old_selection.saturating_sub( - self.current_height.get().saturating_sub(1), - ), - ScrollType::PageDown => old_selection - .saturating_add( - self.current_height.get().saturating_sub(1), - ) - .min(max_selection), - }; + let new_selection = match scroll_type { + ScrollType::Up => old_selection.saturating_sub(1), + ScrollType::Down => { + old_selection.saturating_add(1).min(max_selection) + } + ScrollType::Home => 0, + ScrollType::End => max_selection, + ScrollType::PageUp => old_selection.saturating_sub( + self.current_height.get().saturating_sub(1), + ), + ScrollType::PageDown => old_selection + .saturating_add( + self.current_height.get().saturating_sub(1), + ) + .min(max_selection), + }; - let needs_update = new_selection != old_selection; + let needs_update = new_selection != old_selection; - table_state.select(Some(new_selection)); - self.table_state.set(table_state); + table_state.select(Some(new_selection)); + self.table_state.set(table_state); - needs_update - } + needs_update + } - /// - fn get_rows(&self) -> Vec { - if let Some(ref tags) = self.tags { - tags.iter().map(|tag| self.get_row(tag)).collect() - } else { - vec![] - } - } + /// + fn get_rows(&self) -> Vec { + if let Some(ref tags) = self.tags { + tags.iter().map(|tag| self.get_row(tag)).collect() + } else { + vec![] + } + } - /// - fn get_row(&self, tag: &TagWithMetadata) -> Row { - const UPSTREAM_SYMBOL: &str = "\u{2191}"; - const EMPTY_SYMBOL: &str = " "; + /// + fn get_row(&self, tag: &TagWithMetadata) -> Row { + const UPSTREAM_SYMBOL: &str = "\u{2191}"; + const EMPTY_SYMBOL: &str = " "; - let is_tag_missing_on_remote = self - .missing_remote_tags - .as_ref() - .map_or(false, |missing_remote_tags| { - let remote_tag = format!("refs/tags/{}", tag.name); + let is_tag_missing_on_remote = self + .missing_remote_tags + .as_ref() + .map_or(false, |missing_remote_tags| { + let remote_tag = format!("refs/tags/{}", tag.name); - missing_remote_tags.contains(&remote_tag) - }); + missing_remote_tags.contains(&remote_tag) + }); - let has_remote_str = if is_tag_missing_on_remote { - UPSTREAM_SYMBOL - } else { - EMPTY_SYMBOL - }; + let has_remote_str = if is_tag_missing_on_remote { + UPSTREAM_SYMBOL + } else { + EMPTY_SYMBOL + }; - let cells: Vec = vec![ - Cell::from(has_remote_str) - .style(self.theme.commit_author(false)), - Cell::from(tag.name.clone()) - .style(self.theme.text(true, false)), - Cell::from(utils::time_to_string(tag.time, true)) - .style(self.theme.commit_time(false)), - Cell::from(tag.author.clone()) - .style(self.theme.commit_author(false)), - Cell::from(tag.message.clone()) - .style(self.theme.text(true, false)), - ]; + let cells: Vec = vec![ + Cell::from(has_remote_str) + .style(self.theme.commit_author(false)), + Cell::from(tag.name.clone()) + .style(self.theme.text(true, false)), + Cell::from(utils::time_to_string(tag.time, true)) + .style(self.theme.commit_time(false)), + Cell::from(tag.author.clone()) + .style(self.theme.commit_author(false)), + Cell::from(tag.message.clone()) + .style(self.theme.text(true, false)), + ]; - Row::new(cells) - } + Row::new(cells) + } - fn valid_selection(&self) -> bool { - self.selected_tag().is_some() - } + fn valid_selection(&self) -> bool { + self.selected_tag().is_some() + } - fn selected_tag(&self) -> Option<&TagWithMetadata> { - self.tags.as_ref().and_then(|tags| { - let table_state = self.table_state.take(); + fn selected_tag(&self) -> Option<&TagWithMetadata> { + self.tags.as_ref().and_then(|tags| { + let table_state = self.table_state.take(); - let tag = table_state - .selected() - .and_then(|selected| tags.get(selected)); + let tag = table_state + .selected() + .and_then(|selected| tags.get(selected)); - self.table_state.set(table_state); + self.table_state.set(table_state); - tag - }) - } + tag + }) + } } diff --git a/src/components/textinput.rs b/src/components/textinput.rs index 0eb71031..63c94527 100644 --- a/src/components/textinput.rs +++ b/src/components/textinput.rs @@ -1,545 +1,545 @@ use crate::ui::Size; use crate::{ - components::{ - popup_paragraph, visibility_blocking, CommandBlocking, - CommandInfo, Component, DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - strings, - ui::{self, style::SharedTheme}, + components::{ + popup_paragraph, visibility_blocking, CommandBlocking, + CommandInfo, Component, DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + strings, + ui::{self, style::SharedTheme}, }; use anyhow::Result; use crossterm::event::{Event, KeyCode, KeyModifiers}; use itertools::Itertools; use std::{cell::Cell, collections::HashMap, ops::Range}; use tui::{ - backend::Backend, - layout::{Alignment, Rect}, - style::Modifier, - text::{Spans, Text}, - widgets::{Clear, Paragraph}, - Frame, + backend::Backend, + layout::{Alignment, Rect}, + style::Modifier, + text::{Spans, Text}, + widgets::{Clear, Paragraph}, + Frame, }; #[derive(PartialEq)] pub enum InputType { - Singleline, - Multiline, - Password, + Singleline, + Multiline, + Password, } /// primarily a subcomponet for user input of text (used in `CommitComponent`) pub struct TextInputComponent { - title: String, - default_msg: String, - msg: String, - visible: bool, - show_char_count: bool, - theme: SharedTheme, - key_config: SharedKeyConfig, - cursor_position: usize, - input_type: InputType, - current_area: Cell, + title: String, + default_msg: String, + msg: String, + visible: bool, + show_char_count: bool, + theme: SharedTheme, + key_config: SharedKeyConfig, + cursor_position: usize, + input_type: InputType, + current_area: Cell, } impl TextInputComponent { - /// - pub fn new( - theme: SharedTheme, - key_config: SharedKeyConfig, - title: &str, - default_msg: &str, - show_char_count: bool, - ) -> Self { - Self { - msg: String::new(), - visible: false, - theme, - key_config, - show_char_count, - title: title.to_string(), - default_msg: default_msg.to_string(), - cursor_position: 0, - input_type: InputType::Multiline, - current_area: Cell::new(Rect::default()), - } - } + /// + pub fn new( + theme: SharedTheme, + key_config: SharedKeyConfig, + title: &str, + default_msg: &str, + show_char_count: bool, + ) -> Self { + Self { + msg: String::new(), + visible: false, + theme, + key_config, + show_char_count, + title: title.to_string(), + default_msg: default_msg.to_string(), + cursor_position: 0, + input_type: InputType::Multiline, + current_area: Cell::new(Rect::default()), + } + } - pub const fn with_input_type( - mut self, - input_type: InputType, - ) -> Self { - self.input_type = input_type; - self - } + pub const fn with_input_type( + mut self, + input_type: InputType, + ) -> Self { + self.input_type = input_type; + self + } - /// Clear the `msg`. - pub fn clear(&mut self) { - self.msg.clear(); - self.cursor_position = 0; - } + /// Clear the `msg`. + pub fn clear(&mut self) { + self.msg.clear(); + self.cursor_position = 0; + } - /// Get the `msg`. - pub const fn get_text(&self) -> &String { - &self.msg - } + /// Get the `msg`. + pub const fn get_text(&self) -> &String { + &self.msg + } - /// screen area (last time we got drawn) - pub fn get_area(&self) -> Rect { - self.current_area.get() - } + /// screen area (last time we got drawn) + pub fn get_area(&self) -> Rect { + self.current_area.get() + } - /// Move the cursor right one char. - fn incr_cursor(&mut self) { - if let Some(pos) = self.next_char_position() { - self.cursor_position = pos; - } - } + /// Move the cursor right one char. + fn incr_cursor(&mut self) { + if let Some(pos) = self.next_char_position() { + self.cursor_position = pos; + } + } - /// Move the cursor left one char. - fn decr_cursor(&mut self) { - let mut index = self.cursor_position.saturating_sub(1); - while index > 0 && !self.msg.is_char_boundary(index) { - index -= 1; - } - self.cursor_position = index; - } + /// Move the cursor left one char. + fn decr_cursor(&mut self) { + let mut index = self.cursor_position.saturating_sub(1); + while index > 0 && !self.msg.is_char_boundary(index) { + index -= 1; + } + self.cursor_position = index; + } - /// Get the position of the next char, or, if the cursor points - /// to the last char, the `msg.len()`. - /// Returns None when the cursor is already at `msg.len()`. - fn next_char_position(&self) -> Option { - if self.cursor_position >= self.msg.len() { - return None; - } - let mut index = self.cursor_position.saturating_add(1); - while index < self.msg.len() - && !self.msg.is_char_boundary(index) - { - index += 1; - } - Some(index) - } + /// Get the position of the next char, or, if the cursor points + /// to the last char, the `msg.len()`. + /// Returns None when the cursor is already at `msg.len()`. + fn next_char_position(&self) -> Option { + if self.cursor_position >= self.msg.len() { + return None; + } + let mut index = self.cursor_position.saturating_add(1); + while index < self.msg.len() + && !self.msg.is_char_boundary(index) + { + index += 1; + } + Some(index) + } - fn backspace(&mut self) { - if self.cursor_position > 0 { - self.decr_cursor(); - self.msg.remove(self.cursor_position); - } - } + fn backspace(&mut self) { + if self.cursor_position > 0 { + self.decr_cursor(); + self.msg.remove(self.cursor_position); + } + } - /// Set the `msg`. - pub fn set_text(&mut self, msg: String) { - self.msg = msg; - self.cursor_position = 0; - } + /// Set the `msg`. + pub fn set_text(&mut self, msg: String) { + self.msg = msg; + self.cursor_position = 0; + } - /// Set the `title`. - pub fn set_title(&mut self, t: String) { - self.title = t; - } + /// Set the `title`. + pub fn set_title(&mut self, t: String) { + self.title = t; + } - fn get_draw_text(&self) -> Text { - let style = self.theme.text(true, false); + fn get_draw_text(&self) -> Text { + let style = self.theme.text(true, false); - let mut txt = Text::default(); - // The portion of the text before the cursor is added - // if the cursor is not at the first character. - if self.cursor_position > 0 { - let text_before_cursor = - self.get_msg(0..self.cursor_position); - let ends_in_nl = text_before_cursor.ends_with('\n'); - txt = text_append( - txt, - Text::styled(text_before_cursor, style), - ); - if ends_in_nl { - txt.lines.push(Spans::default()); - } - } + let mut txt = Text::default(); + // The portion of the text before the cursor is added + // if the cursor is not at the first character. + if self.cursor_position > 0 { + let text_before_cursor = + self.get_msg(0..self.cursor_position); + let ends_in_nl = text_before_cursor.ends_with('\n'); + txt = text_append( + txt, + Text::styled(text_before_cursor, style), + ); + if ends_in_nl { + txt.lines.push(Spans::default()); + } + } - let cursor_str = self - .next_char_position() - // if the cursor is at the end of the msg - // a whitespace is used to underline - .map_or(" ".to_owned(), |pos| { - self.get_msg(self.cursor_position..pos) - }); + let cursor_str = self + .next_char_position() + // if the cursor is at the end of the msg + // a whitespace is used to underline + .map_or(" ".to_owned(), |pos| { + self.get_msg(self.cursor_position..pos) + }); - let cursor_highlighting = { - let mut h = HashMap::with_capacity(2); - h.insert("\n", "\u{21b5}\n\r"); - h.insert(" ", "\u{00B7}"); - h - }; + let cursor_highlighting = { + let mut h = HashMap::with_capacity(2); + h.insert("\n", "\u{21b5}\n\r"); + h.insert(" ", "\u{00B7}"); + h + }; - if let Some(substitute) = - cursor_highlighting.get(cursor_str.as_str()) - { - txt = text_append( - txt, - Text::styled( - substitute.to_owned(), - self.theme - .text(false, false) - .add_modifier(Modifier::UNDERLINED), - ), - ); - } else { - txt = text_append( - txt, - Text::styled( - cursor_str, - style.add_modifier(Modifier::UNDERLINED), - ), - ); - } + if let Some(substitute) = + cursor_highlighting.get(cursor_str.as_str()) + { + txt = text_append( + txt, + Text::styled( + substitute.to_owned(), + self.theme + .text(false, false) + .add_modifier(Modifier::UNDERLINED), + ), + ); + } else { + txt = text_append( + txt, + Text::styled( + cursor_str, + style.add_modifier(Modifier::UNDERLINED), + ), + ); + } - // The final portion of the text is added if there are - // still remaining characters. - if let Some(pos) = self.next_char_position() { - if pos < self.msg.len() { - txt = text_append( - txt, - Text::styled( - self.get_msg(pos..self.msg.len()), - style, - ), - ); - } - } + // The final portion of the text is added if there are + // still remaining characters. + if let Some(pos) = self.next_char_position() { + if pos < self.msg.len() { + txt = text_append( + txt, + Text::styled( + self.get_msg(pos..self.msg.len()), + style, + ), + ); + } + } - txt - } + txt + } - fn get_msg(&self, range: Range) -> String { - match self.input_type { - InputType::Password => range.map(|_| "*").join(""), - _ => self.msg[range].to_owned(), - } - } + fn get_msg(&self, range: Range) -> String { + match self.input_type { + InputType::Password => range.map(|_| "*").join(""), + _ => self.msg[range].to_owned(), + } + } - fn draw_char_count(&self, f: &mut Frame, r: Rect) { - let count = self.msg.len(); - if count > 0 { - let w = Paragraph::new(format!("[{} chars]", count)) - .alignment(Alignment::Right); + fn draw_char_count(&self, f: &mut Frame, r: Rect) { + let count = self.msg.len(); + if count > 0 { + let w = Paragraph::new(format!("[{} chars]", count)) + .alignment(Alignment::Right); - let mut rect = { - let mut rect = r; - rect.y += rect.height.saturating_sub(1); - rect - }; + let mut rect = { + let mut rect = r; + rect.y += rect.height.saturating_sub(1); + rect + }; - rect.x += 1; - rect.width = rect.width.saturating_sub(2); - rect.height = rect - .height - .saturating_sub(rect.height.saturating_sub(1)); + rect.x += 1; + rect.width = rect.width.saturating_sub(2); + rect.height = rect + .height + .saturating_sub(rect.height.saturating_sub(1)); - f.render_widget(w, rect); - } - } + f.render_widget(w, rect); + } + } } // merges last line of `txt` with first of `append` so we do not generate unneeded newlines fn text_append<'a>(txt: Text<'a>, append: Text<'a>) -> Text<'a> { - let mut txt = txt; - if let Some(last_line) = txt.lines.last_mut() { - if let Some(first_line) = append.lines.first() { - last_line.0.extend(first_line.0.clone()); - } + let mut txt = txt; + if let Some(last_line) = txt.lines.last_mut() { + if let Some(first_line) = append.lines.first() { + last_line.0.extend(first_line.0.clone()); + } - if append.lines.len() > 1 { - for line in 1..append.lines.len() { - let spans = append.lines[line].clone(); - txt.lines.push(spans); - } - } - } else { - txt = append; - } - txt + if append.lines.len() > 1 { + for line in 1..append.lines.len() { + let spans = append.lines[line].clone(); + txt.lines.push(spans); + } + } + } else { + txt = append; + } + txt } impl DrawableComponent for TextInputComponent { - fn draw( - &self, - f: &mut Frame, - _rect: Rect, - ) -> Result<()> { - if self.visible { - let txt = if self.msg.is_empty() { - Text::styled( - self.default_msg.as_str(), - self.theme.text(false, false), - ) - } else { - self.get_draw_text() - }; + fn draw( + &self, + f: &mut Frame, + _rect: Rect, + ) -> Result<()> { + if self.visible { + let txt = if self.msg.is_empty() { + Text::styled( + self.default_msg.as_str(), + self.theme.text(false, false), + ) + } else { + self.get_draw_text() + }; - let area = match self.input_type { - InputType::Multiline => { - let area = ui::centered_rect(60, 20, f.size()); - ui::rect_inside( - Size::new(10, 3), - f.size().into(), - area, - ) - } - _ => ui::centered_rect_absolute(32, 3, f.size()), - }; + let area = match self.input_type { + InputType::Multiline => { + let area = ui::centered_rect(60, 20, f.size()); + ui::rect_inside( + Size::new(10, 3), + f.size().into(), + area, + ) + } + _ => ui::centered_rect_absolute(32, 3, f.size()), + }; - f.render_widget(Clear, area); - f.render_widget( - popup_paragraph( - self.title.as_str(), - txt, - &self.theme, - true, - ), - area, - ); + f.render_widget(Clear, area); + f.render_widget( + popup_paragraph( + self.title.as_str(), + txt, + &self.theme, + true, + ), + area, + ); - if self.show_char_count { - self.draw_char_count(f, area); - } + if self.show_char_count { + self.draw_char_count(f, area); + } - self.current_area.set(area); - } + self.current_area.set(area); + } - Ok(()) - } + Ok(()) + } } impl Component for TextInputComponent { - fn commands( - &self, - out: &mut Vec, - _force_all: bool, - ) -> CommandBlocking { - out.push( - CommandInfo::new( - strings::commands::close_popup(&self.key_config), - true, - self.visible, - ) - .order(1), - ); - visibility_blocking(self) - } + fn commands( + &self, + out: &mut Vec, + _force_all: bool, + ) -> CommandBlocking { + out.push( + CommandInfo::new( + strings::commands::close_popup(&self.key_config), + true, + self.visible, + ) + .order(1), + ); + visibility_blocking(self) + } - fn event(&mut self, ev: Event) -> Result { - if self.visible { - if let Event::Key(e) = ev { - if e == self.key_config.exit_popup { - self.hide(); - return Ok(EventState::Consumed); - } + fn event(&mut self, ev: Event) -> Result { + if self.visible { + if let Event::Key(e) = ev { + if e == self.key_config.exit_popup { + self.hide(); + return Ok(EventState::Consumed); + } - let is_ctrl = - e.modifiers.contains(KeyModifiers::CONTROL); + let is_ctrl = + e.modifiers.contains(KeyModifiers::CONTROL); - match e.code { - KeyCode::Char(c) if !is_ctrl => { - self.msg.insert(self.cursor_position, c); - self.incr_cursor(); - return Ok(EventState::Consumed); - } - KeyCode::Delete => { - if self.cursor_position < self.msg.len() { - self.msg.remove(self.cursor_position); - } - return Ok(EventState::Consumed); - } - KeyCode::Backspace => { - self.backspace(); - return Ok(EventState::Consumed); - } - KeyCode::Left => { - self.decr_cursor(); - return Ok(EventState::Consumed); - } - KeyCode::Right => { - self.incr_cursor(); - return Ok(EventState::Consumed); - } - KeyCode::Home => { - self.cursor_position = 0; - return Ok(EventState::Consumed); - } - KeyCode::End => { - self.cursor_position = self.msg.len(); - return Ok(EventState::Consumed); - } - _ => (), - }; - } - } - Ok(EventState::NotConsumed) - } + match e.code { + KeyCode::Char(c) if !is_ctrl => { + self.msg.insert(self.cursor_position, c); + self.incr_cursor(); + return Ok(EventState::Consumed); + } + KeyCode::Delete => { + if self.cursor_position < self.msg.len() { + self.msg.remove(self.cursor_position); + } + return Ok(EventState::Consumed); + } + KeyCode::Backspace => { + self.backspace(); + return Ok(EventState::Consumed); + } + KeyCode::Left => { + self.decr_cursor(); + return Ok(EventState::Consumed); + } + KeyCode::Right => { + self.incr_cursor(); + return Ok(EventState::Consumed); + } + KeyCode::Home => { + self.cursor_position = 0; + return Ok(EventState::Consumed); + } + KeyCode::End => { + self.cursor_position = self.msg.len(); + return Ok(EventState::Consumed); + } + _ => (), + }; + } + } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; + fn show(&mut self) -> Result<()> { + self.visible = true; - Ok(()) - } + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use tui::{style::Style, text::Span}; + use super::*; + use tui::{style::Style, text::Span}; - #[test] - fn test_smoke() { - let mut comp = TextInputComponent::new( - SharedTheme::default(), - SharedKeyConfig::default(), - "", - "", - false, - ); + #[test] + fn test_smoke() { + let mut comp = TextInputComponent::new( + SharedTheme::default(), + SharedKeyConfig::default(), + "", + "", + false, + ); - comp.set_text(String::from("a\nb")); + comp.set_text(String::from("a\nb")); - assert_eq!(comp.cursor_position, 0); + assert_eq!(comp.cursor_position, 0); - comp.incr_cursor(); - assert_eq!(comp.cursor_position, 1); + comp.incr_cursor(); + assert_eq!(comp.cursor_position, 1); - comp.decr_cursor(); - assert_eq!(comp.cursor_position, 0); - } + comp.decr_cursor(); + assert_eq!(comp.cursor_position, 0); + } - #[test] - fn text_cursor_initial_position() { - let mut comp = TextInputComponent::new( - SharedTheme::default(), - SharedKeyConfig::default(), - "", - "", - false, - ); - let theme = SharedTheme::default(); - let underlined = theme - .text(true, false) - .add_modifier(Modifier::UNDERLINED); + #[test] + fn text_cursor_initial_position() { + let mut comp = TextInputComponent::new( + SharedTheme::default(), + SharedKeyConfig::default(), + "", + "", + false, + ); + let theme = SharedTheme::default(); + let underlined = theme + .text(true, false) + .add_modifier(Modifier::UNDERLINED); - comp.set_text(String::from("a")); + comp.set_text(String::from("a")); - let txt = comp.get_draw_text(); + let txt = comp.get_draw_text(); - assert_eq!(txt.lines[0].0.len(), 1); - assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); - assert_eq!(get_style(&txt.lines[0].0[0]), Some(&underlined)); - } + assert_eq!(txt.lines[0].0.len(), 1); + assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); + assert_eq!(get_style(&txt.lines[0].0[0]), Some(&underlined)); + } - #[test] - fn test_cursor_second_position() { - let mut comp = TextInputComponent::new( - SharedTheme::default(), - SharedKeyConfig::default(), - "", - "", - false, - ); - let theme = SharedTheme::default(); - let underlined_whitespace = theme - .text(false, false) - .add_modifier(Modifier::UNDERLINED); + #[test] + fn test_cursor_second_position() { + let mut comp = TextInputComponent::new( + SharedTheme::default(), + SharedKeyConfig::default(), + "", + "", + false, + ); + let theme = SharedTheme::default(); + let underlined_whitespace = theme + .text(false, false) + .add_modifier(Modifier::UNDERLINED); - let not_underlined = Style::default(); + let not_underlined = Style::default(); - comp.set_text(String::from("a")); - comp.incr_cursor(); + comp.set_text(String::from("a")); + comp.incr_cursor(); - let txt = comp.get_draw_text(); + let txt = comp.get_draw_text(); - assert_eq!(txt.lines[0].0.len(), 2); - assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); - assert_eq!( - get_style(&txt.lines[0].0[0]), - Some(¬_underlined) - ); - assert_eq!(get_text(&txt.lines[0].0[1]), Some("\u{00B7}")); - assert_eq!( - get_style(&txt.lines[0].0[1]), - Some(&underlined_whitespace) - ); - } + assert_eq!(txt.lines[0].0.len(), 2); + assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); + assert_eq!( + get_style(&txt.lines[0].0[0]), + Some(¬_underlined) + ); + assert_eq!(get_text(&txt.lines[0].0[1]), Some("\u{00B7}")); + assert_eq!( + get_style(&txt.lines[0].0[1]), + Some(&underlined_whitespace) + ); + } - #[test] - fn test_visualize_newline() { - let mut comp = TextInputComponent::new( - SharedTheme::default(), - SharedKeyConfig::default(), - "", - "", - false, - ); + #[test] + fn test_visualize_newline() { + let mut comp = TextInputComponent::new( + SharedTheme::default(), + SharedKeyConfig::default(), + "", + "", + false, + ); - let theme = SharedTheme::default(); - let underlined = theme - .text(false, false) - .add_modifier(Modifier::UNDERLINED); + let theme = SharedTheme::default(); + let underlined = theme + .text(false, false) + .add_modifier(Modifier::UNDERLINED); - comp.set_text(String::from("a\nb")); - comp.incr_cursor(); + comp.set_text(String::from("a\nb")); + comp.incr_cursor(); - let txt = comp.get_draw_text(); + let txt = comp.get_draw_text(); - assert_eq!(txt.lines.len(), 2); - assert_eq!(txt.lines[0].0.len(), 2); - assert_eq!(txt.lines[1].0.len(), 2); - assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); - assert_eq!(get_text(&txt.lines[0].0[1]), Some("\u{21b5}")); - assert_eq!(get_style(&txt.lines[0].0[1]), Some(&underlined)); - assert_eq!(get_text(&txt.lines[1].0[0]), Some("")); - assert_eq!(get_text(&txt.lines[1].0[1]), Some("b")); - } + assert_eq!(txt.lines.len(), 2); + assert_eq!(txt.lines[0].0.len(), 2); + assert_eq!(txt.lines[1].0.len(), 2); + assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); + assert_eq!(get_text(&txt.lines[0].0[1]), Some("\u{21b5}")); + assert_eq!(get_style(&txt.lines[0].0[1]), Some(&underlined)); + assert_eq!(get_text(&txt.lines[1].0[0]), Some("")); + assert_eq!(get_text(&txt.lines[1].0[1]), Some("b")); + } - #[test] - fn test_invisible_newline() { - let mut comp = TextInputComponent::new( - SharedTheme::default(), - SharedKeyConfig::default(), - "", - "", - false, - ); + #[test] + fn test_invisible_newline() { + let mut comp = TextInputComponent::new( + SharedTheme::default(), + SharedKeyConfig::default(), + "", + "", + false, + ); - let theme = SharedTheme::default(); - let underlined = theme - .text(true, false) - .add_modifier(Modifier::UNDERLINED); + let theme = SharedTheme::default(); + let underlined = theme + .text(true, false) + .add_modifier(Modifier::UNDERLINED); - comp.set_text(String::from("a\nb")); + comp.set_text(String::from("a\nb")); - let txt = comp.get_draw_text(); + let txt = comp.get_draw_text(); - assert_eq!(txt.lines.len(), 2); - assert_eq!(txt.lines[0].0.len(), 2); - assert_eq!(txt.lines[1].0.len(), 1); - assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); - assert_eq!(get_text(&txt.lines[0].0[1]), Some("")); - assert_eq!(get_style(&txt.lines[0].0[0]), Some(&underlined)); - assert_eq!(get_text(&txt.lines[1].0[0]), Some("b")); - } + assert_eq!(txt.lines.len(), 2); + assert_eq!(txt.lines[0].0.len(), 2); + assert_eq!(txt.lines[1].0.len(), 1); + assert_eq!(get_text(&txt.lines[0].0[0]), Some("a")); + assert_eq!(get_text(&txt.lines[0].0[1]), Some("")); + assert_eq!(get_style(&txt.lines[0].0[0]), Some(&underlined)); + assert_eq!(get_text(&txt.lines[1].0[0]), Some("b")); + } - fn get_text<'a>(t: &'a Span) -> Option<&'a str> { - Some(&t.content) - } + fn get_text<'a>(t: &'a Span) -> Option<&'a str> { + Some(&t.content) + } - fn get_style<'a>(t: &'a Span) -> Option<&'a Style> { - Some(&t.style) - } + fn get_style<'a>(t: &'a Span) -> Option<&'a Style> { + Some(&t.style) + } } diff --git a/src/components/utils/filetree.rs b/src/components/utils/filetree.rs index 3dbfc78c..4b26825e 100644 --- a/src/components/utils/filetree.rs +++ b/src/components/utils/filetree.rs @@ -3,39 +3,39 @@ use anyhow::{bail, Result}; use asyncgit::StatusItem; use std::{ - collections::BTreeSet, - convert::TryFrom, - ffi::OsStr, - ops::{Index, IndexMut}, - path::Path, + collections::BTreeSet, + convert::TryFrom, + ffi::OsStr, + ops::{Index, IndexMut}, + path::Path, }; /// holds the information shared among all `FileTreeItem` in a `FileTree` #[derive(Debug, Clone)] pub struct TreeItemInfo { - /// indent level - pub indent: u8, - /// currently visible depending on the folder collapse states - pub visible: bool, - /// just the last path element - pub path: String, - /// the full path - pub full_path: String, + /// indent level + pub indent: u8, + /// currently visible depending on the folder collapse states + pub visible: bool, + /// just the last path element + pub path: String, + /// the full path + pub full_path: String, } impl TreeItemInfo { - const fn new( - indent: u8, - path: String, - full_path: String, - ) -> Self { - Self { - indent, - visible: true, - path, - full_path, - } - } + const fn new( + indent: u8, + path: String, + full_path: String, + ) -> Self { + Self { + indent, + visible: true, + path, + full_path, + } + } } /// attribute used to indicate the collapse/expand state of a path item @@ -45,387 +45,387 @@ pub struct PathCollapsed(pub bool); /// `FileTreeItem` can be of two kinds #[derive(PartialEq, Debug, Clone)] pub enum FileTreeItemKind { - Path(PathCollapsed), - File(StatusItem), + Path(PathCollapsed), + File(StatusItem), } /// `FileTreeItem` can be of two kinds: see `FileTreeItem` but shares an info #[derive(Debug, Clone)] pub struct FileTreeItem { - pub info: TreeItemInfo, - pub kind: FileTreeItemKind, + pub info: TreeItemInfo, + pub kind: FileTreeItemKind, } impl FileTreeItem { - fn new_file(item: &StatusItem) -> Result { - let item_path = Path::new(&item.path); - let indent = u8::try_from( - item_path.ancestors().count().saturating_sub(2), - )?; + fn new_file(item: &StatusItem) -> Result { + let item_path = Path::new(&item.path); + let indent = u8::try_from( + item_path.ancestors().count().saturating_sub(2), + )?; - let name = item_path - .file_name() - .map(OsStr::to_string_lossy) - .map(|x| x.to_string()); + let name = item_path + .file_name() + .map(OsStr::to_string_lossy) + .map(|x| x.to_string()); - match name { - Some(path) => Ok(Self { - info: TreeItemInfo::new( - indent, - path, - item.path.clone(), - ), - kind: FileTreeItemKind::File(item.clone()), - }), - None => bail!("invalid file name {:?}", item), - } - } + match name { + Some(path) => Ok(Self { + info: TreeItemInfo::new( + indent, + path, + item.path.clone(), + ), + kind: FileTreeItemKind::File(item.clone()), + }), + None => bail!("invalid file name {:?}", item), + } + } - fn new_path( - path: &Path, - path_string: String, - collapsed: bool, - ) -> Result { - let indent = - u8::try_from(path.ancestors().count().saturating_sub(2))?; + fn new_path( + path: &Path, + path_string: String, + collapsed: bool, + ) -> Result { + let indent = + u8::try_from(path.ancestors().count().saturating_sub(2))?; - match path - .components() - .last() - .map(std::path::Component::as_os_str) - .map(OsStr::to_string_lossy) - .map(String::from) - { - Some(path) => Ok(Self { - info: TreeItemInfo::new(indent, path, path_string), - kind: FileTreeItemKind::Path(PathCollapsed( - collapsed, - )), - }), - None => bail!("failed to create item from path"), - } - } + match path + .components() + .last() + .map(std::path::Component::as_os_str) + .map(OsStr::to_string_lossy) + .map(String::from) + { + Some(path) => Ok(Self { + info: TreeItemInfo::new(indent, path, path_string), + kind: FileTreeItemKind::Path(PathCollapsed( + collapsed, + )), + }), + None => bail!("failed to create item from path"), + } + } } impl Eq for FileTreeItem {} impl PartialEq for FileTreeItem { - fn eq(&self, other: &Self) -> bool { - self.info.full_path.eq(&other.info.full_path) - } + fn eq(&self, other: &Self) -> bool { + self.info.full_path.eq(&other.info.full_path) + } } impl PartialOrd for FileTreeItem { - fn partial_cmp( - &self, - other: &Self, - ) -> Option { - self.info.full_path.partial_cmp(&other.info.full_path) - } + fn partial_cmp( + &self, + other: &Self, + ) -> Option { + self.info.full_path.partial_cmp(&other.info.full_path) + } } impl Ord for FileTreeItem { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.info.path.cmp(&other.info.path) - } + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.info.path.cmp(&other.info.path) + } } /// #[derive(Default)] pub struct FileTreeItems { - items: Vec, - file_count: usize, + items: Vec, + file_count: usize, } impl FileTreeItems { - /// - pub(crate) fn new( - list: &[StatusItem], - collapsed: &BTreeSet<&String>, - ) -> Result { - let mut items = Vec::with_capacity(list.len()); - let mut paths_added = BTreeSet::new(); + /// + pub(crate) fn new( + list: &[StatusItem], + collapsed: &BTreeSet<&String>, + ) -> Result { + let mut items = Vec::with_capacity(list.len()); + let mut paths_added = BTreeSet::new(); - for e in list { - { - let item_path = Path::new(&e.path); + for e in list { + { + let item_path = Path::new(&e.path); - Self::push_dirs( - item_path, - &mut items, - &mut paths_added, - collapsed, - )?; - } + Self::push_dirs( + item_path, + &mut items, + &mut paths_added, + collapsed, + )?; + } - items.push(FileTreeItem::new_file(e)?); - } + items.push(FileTreeItem::new_file(e)?); + } - Ok(Self { - items, - file_count: list.len(), - }) - } + Ok(Self { + items, + file_count: list.len(), + }) + } - /// - pub(crate) const fn items(&self) -> &Vec { - &self.items - } + /// + pub(crate) const fn items(&self) -> &Vec { + &self.items + } - /// - pub(crate) fn len(&self) -> usize { - self.items.len() - } + /// + pub(crate) fn len(&self) -> usize { + self.items.len() + } - /// - pub const fn file_count(&self) -> usize { - self.file_count - } + /// + pub const fn file_count(&self) -> usize { + self.file_count + } - /// - pub(crate) fn find_parent_index(&self, index: usize) -> usize { - let item_indent = &self.items[index].info.indent; - let mut parent_index = index; - while item_indent <= &self.items[parent_index].info.indent { - if parent_index == 0 { - return 0; - } - parent_index -= 1; - } + /// + pub(crate) fn find_parent_index(&self, index: usize) -> usize { + let item_indent = &self.items[index].info.indent; + let mut parent_index = index; + while item_indent <= &self.items[parent_index].info.indent { + if parent_index == 0 { + return 0; + } + parent_index -= 1; + } - parent_index - } + parent_index + } - fn push_dirs<'a>( - item_path: &'a Path, - nodes: &mut Vec, - paths_added: &mut BTreeSet<&'a Path>, - collapsed: &BTreeSet<&String>, - ) -> Result<()> { - let mut ancestors = - { item_path.ancestors().skip(1).collect::>() }; - ancestors.reverse(); + fn push_dirs<'a>( + item_path: &'a Path, + nodes: &mut Vec, + paths_added: &mut BTreeSet<&'a Path>, + collapsed: &BTreeSet<&String>, + ) -> Result<()> { + let mut ancestors = + { item_path.ancestors().skip(1).collect::>() }; + ancestors.reverse(); - for c in &ancestors { - if c.parent().is_some() && !paths_added.contains(c) { - paths_added.insert(c); - //TODO: get rid of expect - let path_string = - String::from(c.to_str().expect("invalid path")); - let is_collapsed = collapsed.contains(&path_string); - nodes.push(FileTreeItem::new_path( - c, - path_string, - is_collapsed, - )?); - } - } + for c in &ancestors { + if c.parent().is_some() && !paths_added.contains(c) { + paths_added.insert(c); + //TODO: get rid of expect + let path_string = + String::from(c.to_str().expect("invalid path")); + let is_collapsed = collapsed.contains(&path_string); + nodes.push(FileTreeItem::new_path( + c, + path_string, + is_collapsed, + )?); + } + } - Ok(()) - } + Ok(()) + } - pub fn multiple_items_at_path(&self, index: usize) -> bool { - let tree_items = self.items(); - let mut idx_temp_inner; - if index + 2 < tree_items.len() { - idx_temp_inner = index + 1; - while idx_temp_inner < tree_items.len().saturating_sub(1) - && tree_items[index].info.indent - < tree_items[idx_temp_inner].info.indent - { - idx_temp_inner += 1; - } - } else { - return false; - } + pub fn multiple_items_at_path(&self, index: usize) -> bool { + let tree_items = self.items(); + let mut idx_temp_inner; + if index + 2 < tree_items.len() { + idx_temp_inner = index + 1; + while idx_temp_inner < tree_items.len().saturating_sub(1) + && tree_items[index].info.indent + < tree_items[idx_temp_inner].info.indent + { + idx_temp_inner += 1; + } + } else { + return false; + } - tree_items[idx_temp_inner].info.indent - == tree_items[index].info.indent - } + tree_items[idx_temp_inner].info.indent + == tree_items[index].info.indent + } } impl IndexMut for FileTreeItems { - fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - &mut self.items[idx] - } + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + &mut self.items[idx] + } } impl Index for FileTreeItems { - type Output = FileTreeItem; + type Output = FileTreeItem; - fn index(&self, idx: usize) -> &Self::Output { - &self.items[idx] - } + fn index(&self, idx: usize) -> &Self::Output { + &self.items[idx] + } } #[cfg(test)] mod tests { - use super::*; - use asyncgit::StatusItemType; + use super::*; + use asyncgit::StatusItemType; - fn string_vec_to_status(items: &[&str]) -> Vec { - items - .iter() - .map(|a| StatusItem { - path: String::from(*a), - status: StatusItemType::Modified, - }) - .collect::>() - } + fn string_vec_to_status(items: &[&str]) -> Vec { + items + .iter() + .map(|a| StatusItem { + path: String::from(*a), + status: StatusItemType::Modified, + }) + .collect::>() + } - #[test] - fn test_simple() { - let items = string_vec_to_status(&[ - "file.txt", // - ]); + #[test] + fn test_simple() { + let items = string_vec_to_status(&[ + "file.txt", // + ]); - let res = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - assert_eq!( - res.items, - vec![FileTreeItem { - info: TreeItemInfo { - path: items[0].path.clone(), - full_path: items[0].path.clone(), - indent: 0, - visible: true, - }, - kind: FileTreeItemKind::File(items[0].clone()) - }] - ); + assert_eq!( + res.items, + vec![FileTreeItem { + info: TreeItemInfo { + path: items[0].path.clone(), + full_path: items[0].path.clone(), + indent: 0, + visible: true, + }, + kind: FileTreeItemKind::File(items[0].clone()) + }] + ); - let items = string_vec_to_status(&[ - "file.txt", // - "file2.txt", // - ]); + let items = string_vec_to_status(&[ + "file.txt", // + "file2.txt", // + ]); - let res = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - assert_eq!(res.items.len(), 2); - assert_eq!(res.items[1].info.path, items[1].path); - } + assert_eq!(res.items.len(), 2); + assert_eq!(res.items[1].info.path, items[1].path); + } - #[test] - fn test_folder() { - let items = string_vec_to_status(&[ - "a/file.txt", // - ]); + #[test] + fn test_folder() { + let items = string_vec_to_status(&[ + "a/file.txt", // + ]); - let res = FileTreeItems::new(&items, &BTreeSet::new()) - .unwrap() - .items - .iter() - .map(|i| i.info.full_path.clone()) - .collect::>(); + let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() + .items + .iter() + .map(|i| i.info.full_path.clone()) + .collect::>(); - assert_eq!( - res, - vec![String::from("a"), items[0].path.clone(),] - ); - } + assert_eq!( + res, + vec![String::from("a"), items[0].path.clone(),] + ); + } - #[test] - fn test_indent() { - let items = string_vec_to_status(&[ - "a/b/file.txt", // - ]); + #[test] + fn test_indent() { + let items = string_vec_to_status(&[ + "a/b/file.txt", // + ]); - let list = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut res = list - .items - .iter() - .map(|i| (i.info.indent, i.info.path.as_str())); + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let mut res = list + .items + .iter() + .map(|i| (i.info.indent, i.info.path.as_str())); - assert_eq!(res.next(), Some((0, "a"))); - assert_eq!(res.next(), Some((1, "b"))); - assert_eq!(res.next(), Some((2, "file.txt"))); - } + assert_eq!(res.next(), Some((0, "a"))); + assert_eq!(res.next(), Some((1, "b"))); + assert_eq!(res.next(), Some((2, "file.txt"))); + } - #[test] - fn test_indent_folder_file_name() { - let items = string_vec_to_status(&[ - "a/b", // - "a.txt", // - ]); + #[test] + fn test_indent_folder_file_name() { + let items = string_vec_to_status(&[ + "a/b", // + "a.txt", // + ]); - let list = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut res = list - .items - .iter() - .map(|i| (i.info.indent, i.info.path.as_str())); + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let mut res = list + .items + .iter() + .map(|i| (i.info.indent, i.info.path.as_str())); - assert_eq!(res.next(), Some((0, "a"))); - assert_eq!(res.next(), Some((1, "b"))); - assert_eq!(res.next(), Some((0, "a.txt"))); - } + assert_eq!(res.next(), Some((0, "a"))); + assert_eq!(res.next(), Some((1, "b"))); + assert_eq!(res.next(), Some((0, "a.txt"))); + } - #[test] - fn test_folder_dup() { - let items = string_vec_to_status(&[ - "a/file.txt", // - "a/file2.txt", // - ]); + #[test] + fn test_folder_dup() { + let items = string_vec_to_status(&[ + "a/file.txt", // + "a/file2.txt", // + ]); - let res = FileTreeItems::new(&items, &BTreeSet::new()) - .unwrap() - .items - .iter() - .map(|i| i.info.full_path.clone()) - .collect::>(); + let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() + .items + .iter() + .map(|i| i.info.full_path.clone()) + .collect::>(); - assert_eq!( - res, - vec![ - String::from("a"), - items[0].path.clone(), - items[1].path.clone() - ] - ); - } + assert_eq!( + res, + vec![ + String::from("a"), + items[0].path.clone(), + items[1].path.clone() + ] + ); + } - #[test] - fn test_multiple_items_at_path() { - //0 a/ - //1 b/ - //2 c/ - //3 d - //4 e/ - //5 f + #[test] + fn test_multiple_items_at_path() { + //0 a/ + //1 b/ + //2 c/ + //3 d + //4 e/ + //5 f - let res = FileTreeItems::new( - &string_vec_to_status(&[ - "a/b/c/d", // - "a/b/e/f", // - ]), - &BTreeSet::new(), - ) - .unwrap(); + let res = FileTreeItems::new( + &string_vec_to_status(&[ + "a/b/c/d", // + "a/b/e/f", // + ]), + &BTreeSet::new(), + ) + .unwrap(); - assert_eq!(res.multiple_items_at_path(0), false); - assert_eq!(res.multiple_items_at_path(1), false); - assert_eq!(res.multiple_items_at_path(2), true); - } + assert_eq!(res.multiple_items_at_path(0), false); + assert_eq!(res.multiple_items_at_path(1), false); + assert_eq!(res.multiple_items_at_path(2), true); + } - #[test] - fn test_find_parent() { - //0 a/ - //1 b/ - //2 c - //3 d + #[test] + fn test_find_parent() { + //0 a/ + //1 b/ + //2 c + //3 d - let res = FileTreeItems::new( - &string_vec_to_status(&[ - "a/b/c", // - "a/b/d", // - ]), - &BTreeSet::new(), - ) - .unwrap(); + let res = FileTreeItems::new( + &string_vec_to_status(&[ + "a/b/c", // + "a/b/d", // + ]), + &BTreeSet::new(), + ) + .unwrap(); - assert_eq!(res.find_parent_index(3), 1); - } + assert_eq!(res.find_parent_index(3), 1); + } } diff --git a/src/components/utils/logitems.rs b/src/components/utils/logitems.rs index 9247dce6..7008319a 100644 --- a/src/components/utils/logitems.rs +++ b/src/components/utils/logitems.rs @@ -5,96 +5,96 @@ use std::slice::Iter; static SLICE_OFFSET_RELOAD_THRESHOLD: usize = 100; pub struct LogEntry { - pub time: DateTime, - pub author: String, - pub msg: String, - pub hash_short: String, - pub id: CommitId, + pub time: DateTime, + pub author: String, + pub msg: String, + pub hash_short: String, + pub id: CommitId, } impl From for LogEntry { - fn from(c: CommitInfo) -> Self { - let time = - DateTime::::from(DateTime::::from_utc( - NaiveDateTime::from_timestamp(c.time, 0), - Utc, - )); - Self { - author: c.author, - msg: c.message, - time, - hash_short: c.id.get_short_string(), - id: c.id, - } - } + fn from(c: CommitInfo) -> Self { + let time = + DateTime::::from(DateTime::::from_utc( + NaiveDateTime::from_timestamp(c.time, 0), + Utc, + )); + Self { + author: c.author, + msg: c.message, + time, + hash_short: c.id.get_short_string(), + id: c.id, + } + } } impl LogEntry { - pub fn time_to_string(&self, now: DateTime) -> String { - let delta = now - self.time; - if delta < Duration::minutes(30) { - let delta_str = if delta < Duration::minutes(1) { - "<1m ago".to_string() - } else { - format!("{:0>2}m ago", delta.num_minutes()) - }; - format!("{: <10}", delta_str) - } else if self.time.date() == now.date() { - self.time.format("%T ").to_string() - } else { - self.time.format("%Y-%m-%d").to_string() - } - } + pub fn time_to_string(&self, now: DateTime) -> String { + let delta = now - self.time; + if delta < Duration::minutes(30) { + let delta_str = if delta < Duration::minutes(1) { + "<1m ago".to_string() + } else { + format!("{:0>2}m ago", delta.num_minutes()) + }; + format!("{: <10}", delta_str) + } else if self.time.date() == now.date() { + self.time.format("%T ").to_string() + } else { + self.time.format("%Y-%m-%d").to_string() + } + } } /// #[derive(Default)] pub struct ItemBatch { - index_offset: usize, - items: Vec, + index_offset: usize, + items: Vec, } impl ItemBatch { - fn last_idx(&self) -> usize { - self.index_offset + self.items.len() - } + fn last_idx(&self) -> usize { + self.index_offset + self.items.len() + } - /// - pub const fn index_offset(&self) -> usize { - self.index_offset - } + /// + pub const fn index_offset(&self) -> usize { + self.index_offset + } - /// shortcut to get an `Iter` of our internal items - pub fn iter(&self) -> Iter<'_, LogEntry> { - self.items.iter() - } + /// shortcut to get an `Iter` of our internal items + pub fn iter(&self) -> Iter<'_, LogEntry> { + self.items.iter() + } - /// clear curent list of items - pub fn clear(&mut self) { - self.items.clear(); - } + /// clear curent list of items + pub fn clear(&mut self) { + self.items.clear(); + } - /// insert new batch of items - pub fn set_items( - &mut self, - start_index: usize, - commits: Vec, - ) { - self.items.clear(); - self.items.extend(commits.into_iter().map(LogEntry::from)); - self.index_offset = start_index; - } + /// insert new batch of items + pub fn set_items( + &mut self, + start_index: usize, + commits: Vec, + ) { + self.items.clear(); + self.items.extend(commits.into_iter().map(LogEntry::from)); + self.index_offset = start_index; + } - /// returns `true` if we should fetch updated list of items - pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool { - let want_min = - idx.saturating_sub(SLICE_OFFSET_RELOAD_THRESHOLD); - let want_max = idx - .saturating_add(SLICE_OFFSET_RELOAD_THRESHOLD) - .min(idx_max); + /// returns `true` if we should fetch updated list of items + pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool { + let want_min = + idx.saturating_sub(SLICE_OFFSET_RELOAD_THRESHOLD); + let want_max = idx + .saturating_add(SLICE_OFFSET_RELOAD_THRESHOLD) + .min(idx_max); - let needs_data_top = want_min < self.index_offset; - let needs_data_bottom = want_max >= self.last_idx(); - needs_data_bottom || needs_data_top - } + let needs_data_top = want_min < self.index_offset; + let needs_data_bottom = want_max >= self.last_idx(); + needs_data_bottom || needs_data_top + } } diff --git a/src/components/utils/mod.rs b/src/components/utils/mod.rs index 6d1adb4f..ab45bbea 100644 --- a/src/components/utils/mod.rs +++ b/src/components/utils/mod.rs @@ -10,50 +10,50 @@ pub mod statustree; /// It will show a popup in that case #[macro_export] macro_rules! try_or_popup { - ($self:ident, $msg:literal, $e:expr) => { - if let Err(err) = $e { - ::log::error!("{} {}", $msg, err); - $self.queue.push(InternalEvent::ShowErrorMsg(format!( - "{}\n{}", - $msg, err - ))); - } - }; + ($self:ident, $msg:literal, $e:expr) => { + if let Err(err) = $e { + ::log::error!("{} {}", $msg, err); + $self.queue.push(InternalEvent::ShowErrorMsg(format!( + "{}\n{}", + $msg, err + ))); + } + }; } /// helper func to convert unix time since epoch to formated time string in local timezone pub fn time_to_string(secs: i64, short: bool) -> String { - let time = DateTime::::from(DateTime::::from_utc( - NaiveDateTime::from_timestamp(secs, 0), - Utc, - )); - time.format(if short { - "%Y-%m-%d" - } else { - "%Y-%m-%d %H:%M:%S" - }) - .to_string() + let time = DateTime::::from(DateTime::::from_utc( + NaiveDateTime::from_timestamp(secs, 0), + Utc, + )); + time.format(if short { + "%Y-%m-%d" + } else { + "%Y-%m-%d %H:%M:%S" + }) + .to_string() } #[inline] pub fn string_width_align(s: &str, width: usize) -> String { - static POSTFIX: &str = ".."; + static POSTFIX: &str = ".."; - let len = UnicodeWidthStr::width(s); - let width_wo_postfix = width.saturating_sub(POSTFIX.len()); + let len = UnicodeWidthStr::width(s); + let width_wo_postfix = width.saturating_sub(POSTFIX.len()); - if (len >= width_wo_postfix && len <= width) - || (len <= width_wo_postfix) - { - format!("{:w$}", s, w = width) - } else { - let mut s = s.to_string(); - s.truncate(find_truncate_point(&s, width_wo_postfix)); - format!("{}{}", s, POSTFIX) - } + if (len >= width_wo_postfix && len <= width) + || (len <= width_wo_postfix) + { + format!("{:w$}", s, w = width) + } else { + let mut s = s.to_string(); + s.truncate(find_truncate_point(&s, width_wo_postfix)); + format!("{}{}", s, POSTFIX) + } } #[inline] fn find_truncate_point(s: &str, chars: usize) -> usize { - s.chars().take(chars).map(char::len_utf8).sum() + s.chars().take(chars).map(char::len_utf8).sum() } diff --git a/src/components/utils/scroll_vertical.rs b/src/components/utils/scroll_vertical.rs index e47d77a4..54fa7e1b 100644 --- a/src/components/utils/scroll_vertical.rs +++ b/src/components/utils/scroll_vertical.rs @@ -3,136 +3,136 @@ use std::cell::Cell; use tui::{backend::Backend, layout::Rect, Frame}; use crate::{ - components::ScrollType, - ui::{draw_scrollbar, style::SharedTheme}, + components::ScrollType, + ui::{draw_scrollbar, style::SharedTheme}, }; pub struct VerticalScroll { - top: Cell, - max_top: Cell, + top: Cell, + max_top: Cell, } impl VerticalScroll { - pub const fn new() -> Self { - Self { - top: Cell::new(0), - max_top: Cell::new(0), - } - } + pub const fn new() -> Self { + Self { + top: Cell::new(0), + max_top: Cell::new(0), + } + } - pub fn get_top(&self) -> usize { - self.top.get() - } + pub fn get_top(&self) -> usize { + self.top.get() + } - pub fn reset(&self) { - self.top.set(0); - } + pub fn reset(&self) { + self.top.set(0); + } - pub fn move_top(&self, move_type: ScrollType) -> bool { - let old = self.top.get(); - let max = self.max_top.get(); + pub fn move_top(&self, move_type: ScrollType) -> bool { + let old = self.top.get(); + let max = self.max_top.get(); - let new_scroll_top = match move_type { - ScrollType::Down => old.saturating_add(1), - ScrollType::Up => old.saturating_sub(1), - ScrollType::Home => 0, - ScrollType::End => max, - _ => old, - }; + let new_scroll_top = match move_type { + ScrollType::Down => old.saturating_add(1), + ScrollType::Up => old.saturating_sub(1), + ScrollType::Home => 0, + ScrollType::End => max, + _ => old, + }; - let new_scroll_top = new_scroll_top.clamp(0, max); + let new_scroll_top = new_scroll_top.clamp(0, max); - if new_scroll_top == old { - return false; - } + if new_scroll_top == old { + return false; + } - self.top.set(new_scroll_top); + self.top.set(new_scroll_top); - true - } + true + } - pub fn update( - &self, - selection: usize, - selection_max: usize, - visual_height: usize, - ) -> usize { - let new_top = calc_scroll_top( - self.get_top(), - visual_height, - selection, - selection_max, - ); - self.top.set(new_top); + pub fn update( + &self, + selection: usize, + selection_max: usize, + visual_height: usize, + ) -> usize { + let new_top = calc_scroll_top( + self.get_top(), + visual_height, + selection, + selection_max, + ); + self.top.set(new_top); - if visual_height == 0 { - self.max_top.set(0); - } else { - let new_max = selection_max.saturating_sub(visual_height); - self.max_top.set(new_max); - } + if visual_height == 0 { + self.max_top.set(0); + } else { + let new_max = selection_max.saturating_sub(visual_height); + self.max_top.set(new_max); + } - new_top - } + new_top + } - pub fn update_no_selection( - &self, - line_count: usize, - visual_height: usize, - ) -> usize { - self.update(self.get_top(), line_count, visual_height) - } + pub fn update_no_selection( + &self, + line_count: usize, + visual_height: usize, + ) -> usize { + self.update(self.get_top(), line_count, visual_height) + } - pub fn draw( - &self, - f: &mut Frame, - r: Rect, - theme: &SharedTheme, - ) { - draw_scrollbar( - f, - r, - theme, - self.max_top.get(), - self.top.get(), - ); - } + pub fn draw( + &self, + f: &mut Frame, + r: Rect, + theme: &SharedTheme, + ) { + draw_scrollbar( + f, + r, + theme, + self.max_top.get(), + self.top.get(), + ); + } } const fn calc_scroll_top( - current_top: usize, - height_in_lines: usize, - selection: usize, - selection_max: usize, + current_top: usize, + height_in_lines: usize, + selection: usize, + selection_max: usize, ) -> usize { - if height_in_lines == 0 { - return 0; - } - if selection_max <= height_in_lines { - return 0; - } + if height_in_lines == 0 { + return 0; + } + if selection_max <= height_in_lines { + return 0; + } - if current_top + height_in_lines <= selection { - selection.saturating_sub(height_in_lines) + 1 - } else if current_top > selection { - selection - } else { - current_top - } + if current_top + height_in_lines <= selection { + selection.saturating_sub(height_in_lines) + 1 + } else if current_top > selection { + selection + } else { + current_top + } } #[cfg(test)] mod tests { - use super::*; - use pretty_assertions::assert_eq; + use super::*; + use pretty_assertions::assert_eq; - #[test] - fn test_scroll_no_scroll_to_top() { - assert_eq!(calc_scroll_top(1, 10, 4, 4), 0); - } + #[test] + fn test_scroll_no_scroll_to_top() { + assert_eq!(calc_scroll_top(1, 10, 4, 4), 0); + } - #[test] - fn test_scroll_zero_height() { - assert_eq!(calc_scroll_top(4, 0, 4, 3), 0); - } + #[test] + fn test_scroll_zero_height() { + assert_eq!(calc_scroll_top(4, 0, 4, 3), 0); + } } diff --git a/src/components/utils/statustree.rs b/src/components/utils/statustree.rs index ba8d5a5e..1edc7ce6 100644 --- a/src/components/utils/statustree.rs +++ b/src/components/utils/statustree.rs @@ -1,5 +1,5 @@ use super::filetree::{ - FileTreeItem, FileTreeItemKind, FileTreeItems, PathCollapsed, + FileTreeItem, FileTreeItemKind, FileTreeItems, PathCollapsed, }; use anyhow::Result; use asyncgit::StatusItem; @@ -10,895 +10,895 @@ use std::{cmp, collections::BTreeSet}; /// #[derive(Default)] pub struct StatusTree { - pub tree: FileTreeItems, - pub selection: Option, + pub tree: FileTreeItems, + pub selection: Option, - // some folders may be folded up, this allows jumping - // over folders which are folded into their parent - pub available_selections: Vec, + // some folders may be folded up, this allows jumping + // over folders which are folded into their parent + pub available_selections: Vec, } /// #[derive(Copy, Clone, Debug)] pub enum MoveSelection { - Up, - Down, - Left, - Right, - Home, - End, + Up, + Down, + Left, + Right, + Home, + End, } #[derive(Copy, Clone, Debug)] struct SelectionChange { - new_index: usize, - changes: bool, + new_index: usize, + changes: bool, } impl SelectionChange { - const fn new(new_index: usize, changes: bool) -> Self { - Self { new_index, changes } - } + const fn new(new_index: usize, changes: bool) -> Self { + Self { new_index, changes } + } } impl StatusTree { - /// update tree with a new list, try to retain selection and collapse states - pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { - let last_collapsed = self.all_collapsed(); + /// update tree with a new list, try to retain selection and collapse states + pub fn update(&mut self, list: &[StatusItem]) -> Result<()> { + let last_collapsed = self.all_collapsed(); - let last_selection = - self.selected_item().map(|e| e.info.full_path); - let last_selection_index = self.selection.unwrap_or(0); + let last_selection = + self.selected_item().map(|e| e.info.full_path); + let last_selection_index = self.selection.unwrap_or(0); - self.tree = FileTreeItems::new(list, &last_collapsed)?; - self.selection = last_selection.as_ref().map_or_else( - || self.tree.items().first().map(|_| 0), - |last_selection| { - self.find_last_selection( - last_selection, - last_selection_index, - ) - .or_else(|| self.tree.items().first().map(|_| 0)) - }, - ); + self.tree = FileTreeItems::new(list, &last_collapsed)?; + self.selection = last_selection.as_ref().map_or_else( + || self.tree.items().first().map(|_| 0), + |last_selection| { + self.find_last_selection( + last_selection, + last_selection_index, + ) + .or_else(|| self.tree.items().first().map(|_| 0)) + }, + ); - self.update_visibility(None, 0, true); - self.available_selections = self.setup_available_selections(); + self.update_visibility(None, 0, true); + self.available_selections = self.setup_available_selections(); - //NOTE: now that visibility is set we can make sure selection is visible - if let Some(idx) = self.selection { - self.selection = Some(self.find_visible_idx(idx)); - } + //NOTE: now that visibility is set we can make sure selection is visible + if let Some(idx) = self.selection { + self.selection = Some(self.find_visible_idx(idx)); + } - Ok(()) - } + Ok(()) + } - /// Return which indices can be selected, taking into account that - /// some folders may be folded up into their parent - /// - /// It should be impossible to select a folder which has been folded into its parent - fn setup_available_selections(&self) -> Vec { - // use the same algorithm as in filetree build_vec_text_for_drawing function - let mut should_skip_over: usize = 0; - let mut vec_available_selections: Vec = vec![]; - let tree_items = self.tree.items(); - for index in 0..tree_items.len() { - if should_skip_over > 0 { - should_skip_over -= 1; - continue; - } - let mut idx_temp = index; - vec_available_selections.push(index); + /// Return which indices can be selected, taking into account that + /// some folders may be folded up into their parent + /// + /// It should be impossible to select a folder which has been folded into its parent + fn setup_available_selections(&self) -> Vec { + // use the same algorithm as in filetree build_vec_text_for_drawing function + let mut should_skip_over: usize = 0; + let mut vec_available_selections: Vec = vec![]; + let tree_items = self.tree.items(); + for index in 0..tree_items.len() { + if should_skip_over > 0 { + should_skip_over -= 1; + continue; + } + let mut idx_temp = index; + vec_available_selections.push(index); - while idx_temp < tree_items.len().saturating_sub(2) - && tree_items[idx_temp].info.indent - < tree_items[idx_temp + 1].info.indent - { - // fold up the folder/file - idx_temp += 1; - should_skip_over += 1; + while idx_temp < tree_items.len().saturating_sub(2) + && tree_items[idx_temp].info.indent + < tree_items[idx_temp + 1].info.indent + { + // fold up the folder/file + idx_temp += 1; + should_skip_over += 1; - // don't fold files up - if let FileTreeItemKind::File(_) = - &tree_items[idx_temp].kind - { - should_skip_over -= 1; - break; - } + // don't fold files up + if let FileTreeItemKind::File(_) = + &tree_items[idx_temp].kind + { + should_skip_over -= 1; + break; + } - // don't fold up if more than one folder in folder - if self.tree.multiple_items_at_path(idx_temp) { - should_skip_over -= 1; - break; - } - } - } - vec_available_selections - } + // don't fold up if more than one folder in folder + if self.tree.multiple_items_at_path(idx_temp) { + should_skip_over -= 1; + break; + } + } + } + vec_available_selections + } - fn find_visible_idx(&self, mut idx: usize) -> usize { - while idx > 0 { - if self.is_visible_index(idx) { - break; - } + fn find_visible_idx(&self, mut idx: usize) -> usize { + while idx > 0 { + if self.is_visible_index(idx) { + break; + } - idx -= 1; - } + idx -= 1; + } - idx - } + idx + } - /// - pub fn move_selection(&mut self, dir: MoveSelection) -> bool { - self.selection.map_or(false, |selection| { - let selection_change = match dir { - MoveSelection::Up => { - self.selection_updown(selection, true) - } - MoveSelection::Down => { - self.selection_updown(selection, false) - } - MoveSelection::Left => self.selection_left(selection), - MoveSelection::Right => { - self.selection_right(selection) - } - MoveSelection::Home => SelectionChange::new(0, false), - MoveSelection::End => self.selection_end(), - }; + /// + pub fn move_selection(&mut self, dir: MoveSelection) -> bool { + self.selection.map_or(false, |selection| { + let selection_change = match dir { + MoveSelection::Up => { + self.selection_updown(selection, true) + } + MoveSelection::Down => { + self.selection_updown(selection, false) + } + MoveSelection::Left => self.selection_left(selection), + MoveSelection::Right => { + self.selection_right(selection) + } + MoveSelection::Home => SelectionChange::new(0, false), + MoveSelection::End => self.selection_end(), + }; - let changed_index = - selection_change.new_index != selection; + let changed_index = + selection_change.new_index != selection; - self.selection = Some(selection_change.new_index); + self.selection = Some(selection_change.new_index); - changed_index || selection_change.changes - }) - } + changed_index || selection_change.changes + }) + } - /// - pub fn selected_item(&self) -> Option { - self.selection.map(|i| self.tree[i].clone()) - } + /// + pub fn selected_item(&self) -> Option { + self.selection.map(|i| self.tree[i].clone()) + } - /// - pub fn is_empty(&self) -> bool { - self.tree.items().is_empty() - } + /// + pub fn is_empty(&self) -> bool { + self.tree.items().is_empty() + } - fn all_collapsed(&self) -> BTreeSet<&String> { - let mut res = BTreeSet::new(); + fn all_collapsed(&self) -> BTreeSet<&String> { + let mut res = BTreeSet::new(); - for i in self.tree.items() { - if let FileTreeItemKind::Path(PathCollapsed(collapsed)) = - i.kind - { - if collapsed { - res.insert(&i.info.full_path); - } - } - } + for i in self.tree.items() { + if let FileTreeItemKind::Path(PathCollapsed(collapsed)) = + i.kind + { + if collapsed { + res.insert(&i.info.full_path); + } + } + } - res - } + res + } - fn find_last_selection( - &self, - last_selection: &str, - last_index: usize, - ) -> Option { - if self.is_empty() { - return None; - } + fn find_last_selection( + &self, + last_selection: &str, + last_index: usize, + ) -> Option { + if self.is_empty() { + return None; + } - if let Ok(i) = self.tree.items().binary_search_by(|e| { - e.info.full_path.as_str().cmp(last_selection) - }) { - return Some(i); - } + if let Ok(i) = self.tree.items().binary_search_by(|e| { + e.info.full_path.as_str().cmp(last_selection) + }) { + return Some(i); + } - Some(cmp::min(last_index, self.tree.len() - 1)) - } + Some(cmp::min(last_index, self.tree.len() - 1)) + } - fn selection_updown( - &self, - current_index: usize, - up: bool, - ) -> SelectionChange { - let mut current_index_in_available_selections; - let mut cur_index_find = current_index; - if self.available_selections.is_empty() { - // Go to top - current_index_in_available_selections = 0; - } else { - loop { - if let Some(pos) = self - .available_selections - .iter() - .position(|i| *i == cur_index_find) - { - current_index_in_available_selections = pos; - break; - } + fn selection_updown( + &self, + current_index: usize, + up: bool, + ) -> SelectionChange { + let mut current_index_in_available_selections; + let mut cur_index_find = current_index; + if self.available_selections.is_empty() { + // Go to top + current_index_in_available_selections = 0; + } else { + loop { + if let Some(pos) = self + .available_selections + .iter() + .position(|i| *i == cur_index_find) + { + current_index_in_available_selections = pos; + break; + } - // Find the closest to the index, usually this shouldn't happen - if current_index == 0 { - // This should never happen - current_index_in_available_selections = 0; - break; - } - cur_index_find -= 1; - } - } + // Find the closest to the index, usually this shouldn't happen + if current_index == 0 { + // This should never happen + current_index_in_available_selections = 0; + break; + } + cur_index_find -= 1; + } + } - let mut new_index; + let mut new_index; - loop { - // Use available_selections to go to the correct selection as - // some of the folders may be folded up - new_index = if up { - current_index_in_available_selections = - current_index_in_available_selections - .saturating_sub(1); - self.available_selections - [current_index_in_available_selections] - } else if current_index_in_available_selections - .saturating_add(1) - <= self.available_selections.len().saturating_sub(1) - { - current_index_in_available_selections = - current_index_in_available_selections - .saturating_add(1); - self.available_selections - [current_index_in_available_selections] - } else { - // can't move down anymore - new_index = current_index; - break; - }; + loop { + // Use available_selections to go to the correct selection as + // some of the folders may be folded up + new_index = if up { + current_index_in_available_selections = + current_index_in_available_selections + .saturating_sub(1); + self.available_selections + [current_index_in_available_selections] + } else if current_index_in_available_selections + .saturating_add(1) + <= self.available_selections.len().saturating_sub(1) + { + current_index_in_available_selections = + current_index_in_available_selections + .saturating_add(1); + self.available_selections + [current_index_in_available_selections] + } else { + // can't move down anymore + new_index = current_index; + break; + }; - if self.is_visible_index(new_index) { - break; - } - } - SelectionChange::new(new_index, false) - } + if self.is_visible_index(new_index) { + break; + } + } + SelectionChange::new(new_index, false) + } - fn selection_end(&self) -> SelectionChange { - let items_max = self.tree.len().saturating_sub(1); + fn selection_end(&self) -> SelectionChange { + let items_max = self.tree.len().saturating_sub(1); - let mut new_index = items_max; + let mut new_index = items_max; - loop { - if self.is_visible_index(new_index) { - break; - } + loop { + if self.is_visible_index(new_index) { + break; + } - if new_index == 0 { - break; - } + if new_index == 0 { + break; + } - new_index = new_index.saturating_sub(1); - new_index = cmp::min(new_index, items_max); - } + new_index = new_index.saturating_sub(1); + new_index = cmp::min(new_index, items_max); + } - SelectionChange::new(new_index, false) - } + SelectionChange::new(new_index, false) + } - fn is_visible_index(&self, idx: usize) -> bool { - self.tree[idx].info.visible - } + fn is_visible_index(&self, idx: usize) -> bool { + self.tree[idx].info.visible + } - fn selection_right( - &mut self, - current_selection: usize, - ) -> SelectionChange { - let item_kind = self.tree[current_selection].kind.clone(); - let item_path = - self.tree[current_selection].info.full_path.clone(); + fn selection_right( + &mut self, + current_selection: usize, + ) -> SelectionChange { + let item_kind = self.tree[current_selection].kind.clone(); + let item_path = + self.tree[current_selection].info.full_path.clone(); - match item_kind { - FileTreeItemKind::Path(PathCollapsed(collapsed)) - if collapsed => - { - self.expand(&item_path, current_selection); - return SelectionChange::new(current_selection, true); - } - FileTreeItemKind::Path(PathCollapsed(collapsed)) - if !collapsed => - { - return self - .selection_updown(current_selection, false); - } - _ => (), - } + match item_kind { + FileTreeItemKind::Path(PathCollapsed(collapsed)) + if collapsed => + { + self.expand(&item_path, current_selection); + return SelectionChange::new(current_selection, true); + } + FileTreeItemKind::Path(PathCollapsed(collapsed)) + if !collapsed => + { + return self + .selection_updown(current_selection, false); + } + _ => (), + } - SelectionChange::new(current_selection, false) - } + SelectionChange::new(current_selection, false) + } - fn selection_left( - &mut self, - current_selection: usize, - ) -> SelectionChange { - let item_kind = self.tree[current_selection].kind.clone(); - let item_path = - self.tree[current_selection].info.full_path.clone(); + fn selection_left( + &mut self, + current_selection: usize, + ) -> SelectionChange { + let item_kind = self.tree[current_selection].kind.clone(); + let item_path = + self.tree[current_selection].info.full_path.clone(); - if matches!(item_kind, FileTreeItemKind::File(_)) - || matches!(item_kind,FileTreeItemKind::Path(PathCollapsed(collapsed)) + if matches!(item_kind, FileTreeItemKind::File(_)) + || matches!(item_kind,FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed) - { - let mut cur_parent = - self.tree.find_parent_index(current_selection); - while !self.available_selections.contains(&cur_parent) - && cur_parent != 0 - { - cur_parent = self.tree.find_parent_index(cur_parent); - } - SelectionChange::new(cur_parent, false) - } else if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) + { + let mut cur_parent = + self.tree.find_parent_index(current_selection); + while !self.available_selections.contains(&cur_parent) + && cur_parent != 0 + { + cur_parent = self.tree.find_parent_index(cur_parent); + } + SelectionChange::new(cur_parent, false) + } else if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if !collapsed) - { - self.collapse(&item_path, current_selection); - SelectionChange::new(current_selection, true) - } else { - SelectionChange::new(current_selection, false) - } - } + { + self.collapse(&item_path, current_selection); + SelectionChange::new(current_selection, true) + } else { + SelectionChange::new(current_selection, false) + } + } - fn collapse(&mut self, path: &str, index: usize) { - if let FileTreeItemKind::Path(PathCollapsed( - ref mut collapsed, - )) = self.tree[index].kind - { - *collapsed = true; - } + fn collapse(&mut self, path: &str, index: usize) { + if let FileTreeItemKind::Path(PathCollapsed( + ref mut collapsed, + )) = self.tree[index].kind + { + *collapsed = true; + } - let path = format!("{}/", path); + let path = format!("{}/", path); - for i in index + 1..self.tree.len() { - let item = &mut self.tree[i]; - let item_path = &item.info.full_path; - if item_path.starts_with(&path) { - item.info.visible = false; - } else { - return; - } - } - } + for i in index + 1..self.tree.len() { + let item = &mut self.tree[i]; + let item_path = &item.info.full_path; + if item_path.starts_with(&path) { + item.info.visible = false; + } else { + return; + } + } + } - fn expand(&mut self, path: &str, current_index: usize) { - if let FileTreeItemKind::Path(PathCollapsed( - ref mut collapsed, - )) = self.tree[current_index].kind - { - *collapsed = false; - } + fn expand(&mut self, path: &str, current_index: usize) { + if let FileTreeItemKind::Path(PathCollapsed( + ref mut collapsed, + )) = self.tree[current_index].kind + { + *collapsed = false; + } - let path = format!("{}/", path); + let path = format!("{}/", path); - self.update_visibility( - Some(path.as_str()), - current_index + 1, - false, - ); - } + self.update_visibility( + Some(path.as_str()), + current_index + 1, + false, + ); + } - fn update_visibility( - &mut self, - prefix: Option<&str>, - start_idx: usize, - set_defaults: bool, - ) { - // if we are in any subpath that is collapsed we keep skipping over it - let mut inner_collapsed: Option = None; + fn update_visibility( + &mut self, + prefix: Option<&str>, + start_idx: usize, + set_defaults: bool, + ) { + // if we are in any subpath that is collapsed we keep skipping over it + let mut inner_collapsed: Option = None; - for i in start_idx..self.tree.len() { - if let Some(ref collapsed_path) = inner_collapsed { - let p: &String = &self.tree[i].info.full_path; - if p.starts_with(collapsed_path) { - if set_defaults { - self.tree[i].info.visible = false; - } - // we are still in a collapsed inner path - continue; - } - inner_collapsed = None; - } + for i in start_idx..self.tree.len() { + if let Some(ref collapsed_path) = inner_collapsed { + let p: &String = &self.tree[i].info.full_path; + if p.starts_with(collapsed_path) { + if set_defaults { + self.tree[i].info.visible = false; + } + // we are still in a collapsed inner path + continue; + } + inner_collapsed = None; + } - let item_kind = self.tree[i].kind.clone(); - let item_path = &self.tree[i].info.full_path; + let item_kind = self.tree[i].kind.clone(); + let item_path = &self.tree[i].info.full_path; - if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed) - { - // we encountered an inner path that is still collapsed - inner_collapsed = Some(format!("{}/", &item_path)); - } + if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed) + { + // we encountered an inner path that is still collapsed + inner_collapsed = Some(format!("{}/", &item_path)); + } - if prefix - .map_or(true, |prefix| item_path.starts_with(prefix)) - { - self.tree[i].info.visible = true; - } else { - // if we do not set defaults we can early out - if set_defaults { - self.tree[i].info.visible = false; - } else { - return; - } - } - } - } + if prefix + .map_or(true, |prefix| item_path.starts_with(prefix)) + { + self.tree[i].info.visible = true; + } else { + // if we do not set defaults we can early out + if set_defaults { + self.tree[i].info.visible = false; + } else { + return; + } + } + } + } } #[cfg(test)] mod tests { - use super::*; - use asyncgit::StatusItemType; - - fn string_vec_to_status(items: &[&str]) -> Vec { - items - .iter() - .map(|a| StatusItem { - path: String::from(*a), - status: StatusItemType::Modified, - }) - .collect::>() - } - - fn get_visibles(tree: &StatusTree) -> Vec { - tree.tree - .items() - .iter() - .map(|e| e.info.visible) - .collect::>() - } - - #[test] - fn test_selection() { - let items = string_vec_to_status(&[ - "a/b", // - ]); - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - - assert!(res.move_selection(MoveSelection::Down)); - - assert_eq!(res.selection, Some(1)); - - assert!(res.move_selection(MoveSelection::Left)); - - assert_eq!(res.selection, Some(0)); - } - - #[test] - fn test_keep_selected_item() { - let mut res = StatusTree::default(); - res.update(&string_vec_to_status(&["b"])).unwrap(); - - assert_eq!(res.selection, Some(0)); - - res.update(&string_vec_to_status(&["a", "b"])).unwrap(); - - assert_eq!(res.selection, Some(1)); - } - - #[test] - fn test_keep_selected_index() { - let mut res = StatusTree::default(); - res.update(&string_vec_to_status(&["a", "b"])).unwrap(); - res.selection = Some(1); - - res.update(&string_vec_to_status(&["d", "c", "a"])).unwrap(); - assert_eq!(res.selection, Some(1)); - } - - #[test] - fn test_keep_selected_index_if_not_collapsed() { - let mut res = StatusTree::default(); - res.update(&string_vec_to_status(&["a/b", "c"])).unwrap(); - - res.collapse("a/b", 0); - - res.selection = Some(2); - - res.update(&string_vec_to_status(&["a/b"])).unwrap(); - assert_eq!( - get_visibles(&res), - vec![ - true, // - false, // - ] - ); - assert_eq!( - res.is_visible_index(res.selection.unwrap()), - true - ); - assert_eq!(res.selection, Some(0)); - } - - #[test] - fn test_keep_collapsed_states() { - let mut res = StatusTree::default(); - res.update(&string_vec_to_status(&[ - "a/b", // - "c", - ])) - .unwrap(); - - res.collapse("a", 0); - - assert_eq!( - res.all_collapsed().iter().collect::>(), - vec![&&String::from("a")] - ); - - assert_eq!( - get_visibles(&res), - vec![ - true, // - false, // - true, // - ] - ); - - res.update(&string_vec_to_status(&[ - "a/b", // - "c", // - "d", - ])) - .unwrap(); - - assert_eq!( - res.all_collapsed().iter().collect::>(), - vec![&&String::from("a")] - ); - - assert_eq!( - get_visibles(&res), - vec![ - true, // - false, // - true, // - true - ] - ); - } - - #[test] - fn test_expand() { - let items = string_vec_to_status(&[ - "a/b/c", // - "a/d", // - ]); - - //0 a/ - //1 b/ - //2 c - //3 d - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - - res.collapse(&String::from("a/b"), 1); - - let visibles = get_visibles(&res); - - assert_eq!( - visibles, - vec![ - true, // - true, // - false, // - true, - ] - ); - - res.expand(&String::from("a/b"), 1); - - let visibles = get_visibles(&res); - - assert_eq!( - visibles, - vec![ - true, // - true, // - true, // - true, - ] - ); - } - - #[test] - fn test_expand_bug() { - let items = string_vec_to_status(&[ - "a/b/c", // - "a/b2/d", // - ]); - - //0 a/ - //1 b/ - //2 c - //3 b2/ - //4 d - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - - res.collapse(&String::from("b"), 1); - res.collapse(&String::from("a"), 0); - - assert_eq!( - get_visibles(&res), - vec![ - true, // - false, // - false, // - false, // - false, - ] - ); - - res.expand(&String::from("a"), 0); - - assert_eq!( - get_visibles(&res), - vec![ - true, // - true, // - false, // - true, // - true, - ] - ); - } - - #[test] - fn test_collapse_too_much() { - let items = string_vec_to_status(&[ - "a/b", // - "a2/c", // - ]); - - //0 a/ - //1 b - //2 a2/ - //3 c - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - - res.collapse(&String::from("a"), 0); - - let visibles = get_visibles(&res); - - assert_eq!( - visibles, - vec![ - true, // - false, // - true, // - true, - ] - ); - } - - #[test] - fn test_expand_with_collapsed_sub_parts() { - let items = string_vec_to_status(&[ - "a/b/c", // - "a/d", // - ]); - - //0 a/ - //1 b/ - //2 c - //3 d - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - - res.collapse(&String::from("a/b"), 1); - - let visibles = get_visibles(&res); - - assert_eq!( - visibles, - vec![ - true, // - true, // - false, // - true, - ] - ); - - res.collapse(&String::from("a"), 0); - - let visibles = get_visibles(&res); - - assert_eq!( - visibles, - vec![ - true, // - false, // - false, // - false, - ] - ); - - res.expand(&String::from("a"), 0); - - let visibles = get_visibles(&res); - - assert_eq!( - visibles, - vec![ - true, // - true, // - false, // - true, - ] - ); - } - - #[test] - fn test_selection_skips_collapsed() { - let items = string_vec_to_status(&[ - "a/b/c", // - "a/d", // - ]); - - //0 a/ - //1 b/ - //2 c - //3 d - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - res.collapse(&String::from("a/b"), 1); - res.selection = Some(1); - - assert!(res.move_selection(MoveSelection::Down)); - - assert_eq!(res.selection, Some(3)); - } - - #[test] - fn test_folders_fold_up_if_alone_in_directory() { - let items = string_vec_to_status(&[ - "a/b/c/d", // - "a/e/f/g", // - "a/h/i/j", // - ]); - - //0 a/ - //1 b/ - //2 c/ - //3 d - //4 e/ - //5 f/ - //6 g - //7 h/ - //8 i/ - //9 j - - //0 a/ - //1 b/c/ - //3 d - //4 e/f/ - //6 g - //7 h/i/ - //9 j - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - res.selection = Some(0); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(1)); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(3)); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(4)); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(6)); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(7)); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(9)); - } - - #[test] - fn test_folders_fold_up_if_alone_in_directory_2() { - let items = string_vec_to_status(&["a/b/c/d/e/f/g/h"]); - - //0 a/ - //1 b/ - //2 c/ - //3 d/ - //4 e/ - //5 f/ - //6 g/ - //7 h - - //0 a/b/c/d/e/f/g/ - //7 h - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - res.selection = Some(0); - - assert!(res.move_selection(MoveSelection::Down)); - assert_eq!(res.selection, Some(7)); - } - - #[test] - fn test_folders_fold_up_down_with_selection_left_right() { - let items = string_vec_to_status(&[ - "a/b/c/d", // - "a/e/f/g", // - "a/h/i/j", // - ]); - - //0 a/ - //1 b/ - //2 c/ - //3 d - //4 e/ - //5 f/ - //6 g - //7 h/ - //8 i/ - //9 j - - //0 a/ - //1 b/c/ - //3 d - //4 e/f/ - //6 g - //7 h/i/ - //9 j - - let mut res = StatusTree::default(); - res.update(&items).unwrap(); - res.selection = Some(0); - - assert!(res.move_selection(MoveSelection::Left)); - assert_eq!(res.selection, Some(0)); - - // These should do nothing - res.move_selection(MoveSelection::Left); - res.move_selection(MoveSelection::Left); - assert_eq!(res.selection, Some(0)); - // - assert!(res.move_selection(MoveSelection::Right)); // unfold 0 - assert_eq!(res.selection, Some(0)); - - assert!(res.move_selection(MoveSelection::Right)); // move to 1 - assert_eq!(res.selection, Some(1)); - - assert!(res.move_selection(MoveSelection::Left)); // fold 1 - assert!(res.move_selection(MoveSelection::Down)); // move to 4 - assert_eq!(res.selection, Some(4)); - - assert!(res.move_selection(MoveSelection::Left)); // fold 4 - assert!(res.move_selection(MoveSelection::Down)); // move to 7 - assert_eq!(res.selection, Some(7)); - - assert!(res.move_selection(MoveSelection::Right)); // move to 9 - assert_eq!(res.selection, Some(9)); - - assert!(res.move_selection(MoveSelection::Left)); // move to 7 - assert_eq!(res.selection, Some(7)); - - assert!(res.move_selection(MoveSelection::Left)); // folds 7 - assert_eq!(res.selection, Some(7)); - - assert!(res.move_selection(MoveSelection::Left)); // jump to 0 - assert_eq!(res.selection, Some(0)); - } + use super::*; + use asyncgit::StatusItemType; + + fn string_vec_to_status(items: &[&str]) -> Vec { + items + .iter() + .map(|a| StatusItem { + path: String::from(*a), + status: StatusItemType::Modified, + }) + .collect::>() + } + + fn get_visibles(tree: &StatusTree) -> Vec { + tree.tree + .items() + .iter() + .map(|e| e.info.visible) + .collect::>() + } + + #[test] + fn test_selection() { + let items = string_vec_to_status(&[ + "a/b", // + ]); + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + + assert!(res.move_selection(MoveSelection::Down)); + + assert_eq!(res.selection, Some(1)); + + assert!(res.move_selection(MoveSelection::Left)); + + assert_eq!(res.selection, Some(0)); + } + + #[test] + fn test_keep_selected_item() { + let mut res = StatusTree::default(); + res.update(&string_vec_to_status(&["b"])).unwrap(); + + assert_eq!(res.selection, Some(0)); + + res.update(&string_vec_to_status(&["a", "b"])).unwrap(); + + assert_eq!(res.selection, Some(1)); + } + + #[test] + fn test_keep_selected_index() { + let mut res = StatusTree::default(); + res.update(&string_vec_to_status(&["a", "b"])).unwrap(); + res.selection = Some(1); + + res.update(&string_vec_to_status(&["d", "c", "a"])).unwrap(); + assert_eq!(res.selection, Some(1)); + } + + #[test] + fn test_keep_selected_index_if_not_collapsed() { + let mut res = StatusTree::default(); + res.update(&string_vec_to_status(&["a/b", "c"])).unwrap(); + + res.collapse("a/b", 0); + + res.selection = Some(2); + + res.update(&string_vec_to_status(&["a/b"])).unwrap(); + assert_eq!( + get_visibles(&res), + vec![ + true, // + false, // + ] + ); + assert_eq!( + res.is_visible_index(res.selection.unwrap()), + true + ); + assert_eq!(res.selection, Some(0)); + } + + #[test] + fn test_keep_collapsed_states() { + let mut res = StatusTree::default(); + res.update(&string_vec_to_status(&[ + "a/b", // + "c", + ])) + .unwrap(); + + res.collapse("a", 0); + + assert_eq!( + res.all_collapsed().iter().collect::>(), + vec![&&String::from("a")] + ); + + assert_eq!( + get_visibles(&res), + vec![ + true, // + false, // + true, // + ] + ); + + res.update(&string_vec_to_status(&[ + "a/b", // + "c", // + "d", + ])) + .unwrap(); + + assert_eq!( + res.all_collapsed().iter().collect::>(), + vec![&&String::from("a")] + ); + + assert_eq!( + get_visibles(&res), + vec![ + true, // + false, // + true, // + true + ] + ); + } + + #[test] + fn test_expand() { + let items = string_vec_to_status(&[ + "a/b/c", // + "a/d", // + ]); + + //0 a/ + //1 b/ + //2 c + //3 d + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + + res.collapse(&String::from("a/b"), 1); + + let visibles = get_visibles(&res); + + assert_eq!( + visibles, + vec![ + true, // + true, // + false, // + true, + ] + ); + + res.expand(&String::from("a/b"), 1); + + let visibles = get_visibles(&res); + + assert_eq!( + visibles, + vec![ + true, // + true, // + true, // + true, + ] + ); + } + + #[test] + fn test_expand_bug() { + let items = string_vec_to_status(&[ + "a/b/c", // + "a/b2/d", // + ]); + + //0 a/ + //1 b/ + //2 c + //3 b2/ + //4 d + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + + res.collapse(&String::from("b"), 1); + res.collapse(&String::from("a"), 0); + + assert_eq!( + get_visibles(&res), + vec![ + true, // + false, // + false, // + false, // + false, + ] + ); + + res.expand(&String::from("a"), 0); + + assert_eq!( + get_visibles(&res), + vec![ + true, // + true, // + false, // + true, // + true, + ] + ); + } + + #[test] + fn test_collapse_too_much() { + let items = string_vec_to_status(&[ + "a/b", // + "a2/c", // + ]); + + //0 a/ + //1 b + //2 a2/ + //3 c + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + + res.collapse(&String::from("a"), 0); + + let visibles = get_visibles(&res); + + assert_eq!( + visibles, + vec![ + true, // + false, // + true, // + true, + ] + ); + } + + #[test] + fn test_expand_with_collapsed_sub_parts() { + let items = string_vec_to_status(&[ + "a/b/c", // + "a/d", // + ]); + + //0 a/ + //1 b/ + //2 c + //3 d + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + + res.collapse(&String::from("a/b"), 1); + + let visibles = get_visibles(&res); + + assert_eq!( + visibles, + vec![ + true, // + true, // + false, // + true, + ] + ); + + res.collapse(&String::from("a"), 0); + + let visibles = get_visibles(&res); + + assert_eq!( + visibles, + vec![ + true, // + false, // + false, // + false, + ] + ); + + res.expand(&String::from("a"), 0); + + let visibles = get_visibles(&res); + + assert_eq!( + visibles, + vec![ + true, // + true, // + false, // + true, + ] + ); + } + + #[test] + fn test_selection_skips_collapsed() { + let items = string_vec_to_status(&[ + "a/b/c", // + "a/d", // + ]); + + //0 a/ + //1 b/ + //2 c + //3 d + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + res.collapse(&String::from("a/b"), 1); + res.selection = Some(1); + + assert!(res.move_selection(MoveSelection::Down)); + + assert_eq!(res.selection, Some(3)); + } + + #[test] + fn test_folders_fold_up_if_alone_in_directory() { + let items = string_vec_to_status(&[ + "a/b/c/d", // + "a/e/f/g", // + "a/h/i/j", // + ]); + + //0 a/ + //1 b/ + //2 c/ + //3 d + //4 e/ + //5 f/ + //6 g + //7 h/ + //8 i/ + //9 j + + //0 a/ + //1 b/c/ + //3 d + //4 e/f/ + //6 g + //7 h/i/ + //9 j + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + res.selection = Some(0); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(1)); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(3)); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(4)); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(6)); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(7)); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(9)); + } + + #[test] + fn test_folders_fold_up_if_alone_in_directory_2() { + let items = string_vec_to_status(&["a/b/c/d/e/f/g/h"]); + + //0 a/ + //1 b/ + //2 c/ + //3 d/ + //4 e/ + //5 f/ + //6 g/ + //7 h + + //0 a/b/c/d/e/f/g/ + //7 h + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + res.selection = Some(0); + + assert!(res.move_selection(MoveSelection::Down)); + assert_eq!(res.selection, Some(7)); + } + + #[test] + fn test_folders_fold_up_down_with_selection_left_right() { + let items = string_vec_to_status(&[ + "a/b/c/d", // + "a/e/f/g", // + "a/h/i/j", // + ]); + + //0 a/ + //1 b/ + //2 c/ + //3 d + //4 e/ + //5 f/ + //6 g + //7 h/ + //8 i/ + //9 j + + //0 a/ + //1 b/c/ + //3 d + //4 e/f/ + //6 g + //7 h/i/ + //9 j + + let mut res = StatusTree::default(); + res.update(&items).unwrap(); + res.selection = Some(0); + + assert!(res.move_selection(MoveSelection::Left)); + assert_eq!(res.selection, Some(0)); + + // These should do nothing + res.move_selection(MoveSelection::Left); + res.move_selection(MoveSelection::Left); + assert_eq!(res.selection, Some(0)); + // + assert!(res.move_selection(MoveSelection::Right)); // unfold 0 + assert_eq!(res.selection, Some(0)); + + assert!(res.move_selection(MoveSelection::Right)); // move to 1 + assert_eq!(res.selection, Some(1)); + + assert!(res.move_selection(MoveSelection::Left)); // fold 1 + assert!(res.move_selection(MoveSelection::Down)); // move to 4 + assert_eq!(res.selection, Some(4)); + + assert!(res.move_selection(MoveSelection::Left)); // fold 4 + assert!(res.move_selection(MoveSelection::Down)); // move to 7 + assert_eq!(res.selection, Some(7)); + + assert!(res.move_selection(MoveSelection::Right)); // move to 9 + assert_eq!(res.selection, Some(9)); + + assert!(res.move_selection(MoveSelection::Left)); // move to 7 + assert_eq!(res.selection, Some(7)); + + assert!(res.move_selection(MoveSelection::Left)); // folds 7 + assert_eq!(res.selection, Some(7)); + + assert!(res.move_selection(MoveSelection::Left)); // jump to 0 + assert_eq!(res.selection, Some(0)); + } } diff --git a/src/input.rs b/src/input.rs index 28a2356c..c2523aa2 100644 --- a/src/input.rs +++ b/src/input.rs @@ -3,13 +3,13 @@ use anyhow::Result; use crossbeam_channel::{unbounded, Receiver, Sender}; use crossterm::event::{self, Event}; use std::{ - process, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread, - time::Duration, + process, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, }; static POLL_DURATION: Duration = Duration::from_millis(1000); @@ -17,107 +17,107 @@ static POLL_DURATION: Duration = Duration::from_millis(1000); /// #[derive(Clone, Copy, Debug)] pub enum InputState { - Paused, - Polling, + Paused, + Polling, } /// #[derive(Clone, Copy, Debug)] pub enum InputEvent { - Input(Event), - State(InputState), + Input(Event), + State(InputState), } /// pub struct Input { - desired_state: Arc>, - current_state: Arc, - receiver: Receiver, + desired_state: Arc>, + current_state: Arc, + receiver: Receiver, } impl Input { - /// - pub fn new() -> Self { - let (tx, rx) = unbounded(); + /// + pub fn new() -> Self { + let (tx, rx) = unbounded(); - let desired_state = Arc::new(NotifyableMutex::new(true)); - let current_state = Arc::new(AtomicBool::new(true)); + let desired_state = Arc::new(NotifyableMutex::new(true)); + let current_state = Arc::new(AtomicBool::new(true)); - let arc_desired = Arc::clone(&desired_state); - let arc_current = Arc::clone(¤t_state); + let arc_desired = Arc::clone(&desired_state); + let arc_current = Arc::clone(¤t_state); - thread::spawn(move || { - if let Err(e) = - Self::input_loop(&arc_desired, &arc_current, &tx) - { - log::error!("input thread error: {}", e); - process::abort(); - } - }); + thread::spawn(move || { + if let Err(e) = + Self::input_loop(&arc_desired, &arc_current, &tx) + { + log::error!("input thread error: {}", e); + process::abort(); + } + }); - Self { - receiver: rx, - desired_state, - current_state, - } - } + Self { + receiver: rx, + desired_state, + current_state, + } + } - /// - pub fn receiver(&self) -> Receiver { - self.receiver.clone() - } + /// + pub fn receiver(&self) -> Receiver { + self.receiver.clone() + } - /// - pub fn set_polling(&mut self, enabled: bool) { - self.desired_state.set_and_notify(enabled); - } + /// + pub fn set_polling(&mut self, enabled: bool) { + self.desired_state.set_and_notify(enabled); + } - fn shall_poll(&self) -> bool { - self.desired_state.get() - } + fn shall_poll(&self) -> bool { + self.desired_state.get() + } - /// - pub fn is_state_changing(&self) -> bool { - self.shall_poll() - != self.current_state.load(Ordering::Relaxed) - } + /// + pub fn is_state_changing(&self) -> bool { + self.shall_poll() + != self.current_state.load(Ordering::Relaxed) + } - fn poll(dur: Duration) -> anyhow::Result> { - if event::poll(dur)? { - Ok(Some(event::read()?)) - } else { - Ok(None) - } - } + fn poll(dur: Duration) -> anyhow::Result> { + if event::poll(dur)? { + Ok(Some(event::read()?)) + } else { + Ok(None) + } + } - fn input_loop( - arc_desired: &Arc>, - arc_current: &Arc, - tx: &Sender, - ) -> Result<()> { - loop { - if arc_desired.get() { - if !arc_current.load(Ordering::Relaxed) { - log::info!("input polling resumed"); + fn input_loop( + arc_desired: &Arc>, + arc_current: &Arc, + tx: &Sender, + ) -> Result<()> { + loop { + if arc_desired.get() { + if !arc_current.load(Ordering::Relaxed) { + log::info!("input polling resumed"); - tx.send(InputEvent::State(InputState::Polling))?; - } - arc_current.store(true, Ordering::Relaxed); + tx.send(InputEvent::State(InputState::Polling))?; + } + arc_current.store(true, Ordering::Relaxed); - if let Some(e) = Self::poll(POLL_DURATION)? { - tx.send(InputEvent::Input(e))?; - } - } else { - if arc_current.load(Ordering::Relaxed) { - log::info!("input polling suspended"); + if let Some(e) = Self::poll(POLL_DURATION)? { + tx.send(InputEvent::Input(e))?; + } + } else { + if arc_current.load(Ordering::Relaxed) { + log::info!("input polling suspended"); - tx.send(InputEvent::State(InputState::Paused))?; - } + tx.send(InputEvent::State(InputState::Paused))?; + } - arc_current.store(false, Ordering::Relaxed); + arc_current.store(false, Ordering::Relaxed); - arc_desired.wait(true); - } - } - } + arc_desired.wait(true); + } + } + } } diff --git a/src/keys.rs b/src/keys.rs index 43e37018..e511fc31 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -4,15 +4,15 @@ use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use ron::{ - self, - ser::{to_string_pretty, PrettyConfig}, + self, + ser::{to_string_pretty, PrettyConfig}, }; use serde::{Deserialize, Serialize}; use std::{ - fs::{self, File}, - io::{Read, Write}, - path::PathBuf, - rc::Rc, + fs::{self, File}, + io::{Read, Write}, + path::PathBuf, + rc::Rc, }; use crate::args::get_app_config_path; @@ -21,69 +21,69 @@ pub type SharedKeyConfig = Rc; #[derive(Serialize, Deserialize, Debug)] pub struct KeyConfig { - pub tab_status: KeyEvent, - pub tab_log: KeyEvent, - pub tab_files: KeyEvent, - pub tab_stashing: KeyEvent, - pub tab_stashes: KeyEvent, - pub tab_toggle: KeyEvent, - pub tab_toggle_reverse: KeyEvent, - pub toggle_workarea: KeyEvent, - pub focus_right: KeyEvent, - pub focus_left: KeyEvent, - pub focus_above: KeyEvent, - pub focus_below: KeyEvent, - pub exit: KeyEvent, - pub quit: KeyEvent, - pub exit_popup: KeyEvent, - pub open_commit: KeyEvent, - pub open_commit_editor: KeyEvent, - pub open_help: KeyEvent, - pub move_left: KeyEvent, - pub move_right: KeyEvent, - pub tree_collapse_recursive: KeyEvent, - pub tree_expand_recursive: KeyEvent, - pub home: KeyEvent, - pub end: KeyEvent, - pub move_up: KeyEvent, - pub move_down: KeyEvent, - pub page_down: KeyEvent, - pub page_up: KeyEvent, - pub shift_up: KeyEvent, - pub shift_down: KeyEvent, - pub enter: KeyEvent, - pub blame: KeyEvent, - pub edit_file: KeyEvent, - pub status_stage_all: KeyEvent, - pub status_reset_item: KeyEvent, - pub status_ignore_file: KeyEvent, - pub diff_stage_lines: KeyEvent, - pub diff_reset_lines: KeyEvent, - pub stashing_save: KeyEvent, - pub stashing_toggle_untracked: KeyEvent, - pub stashing_toggle_index: KeyEvent, - pub stash_apply: KeyEvent, - pub stash_open: KeyEvent, - pub stash_drop: KeyEvent, - pub cmd_bar_toggle: KeyEvent, - pub log_tag_commit: KeyEvent, - pub log_mark_commit: KeyEvent, - pub commit_amend: KeyEvent, - pub copy: KeyEvent, - pub create_branch: KeyEvent, - pub rename_branch: KeyEvent, - pub select_branch: KeyEvent, - pub delete_branch: KeyEvent, - pub merge_branch: KeyEvent, - pub tags: KeyEvent, - pub delete_tag: KeyEvent, - pub select_tag: KeyEvent, - pub push: KeyEvent, - pub open_file_tree: KeyEvent, - pub force_push: KeyEvent, - pub pull: KeyEvent, - pub abort_merge: KeyEvent, - pub undo_commit: KeyEvent, + pub tab_status: KeyEvent, + pub tab_log: KeyEvent, + pub tab_files: KeyEvent, + pub tab_stashing: KeyEvent, + pub tab_stashes: KeyEvent, + pub tab_toggle: KeyEvent, + pub tab_toggle_reverse: KeyEvent, + pub toggle_workarea: KeyEvent, + pub focus_right: KeyEvent, + pub focus_left: KeyEvent, + pub focus_above: KeyEvent, + pub focus_below: KeyEvent, + pub exit: KeyEvent, + pub quit: KeyEvent, + pub exit_popup: KeyEvent, + pub open_commit: KeyEvent, + pub open_commit_editor: KeyEvent, + pub open_help: KeyEvent, + pub move_left: KeyEvent, + pub move_right: KeyEvent, + pub tree_collapse_recursive: KeyEvent, + pub tree_expand_recursive: KeyEvent, + pub home: KeyEvent, + pub end: KeyEvent, + pub move_up: KeyEvent, + pub move_down: KeyEvent, + pub page_down: KeyEvent, + pub page_up: KeyEvent, + pub shift_up: KeyEvent, + pub shift_down: KeyEvent, + pub enter: KeyEvent, + pub blame: KeyEvent, + pub edit_file: KeyEvent, + pub status_stage_all: KeyEvent, + pub status_reset_item: KeyEvent, + pub status_ignore_file: KeyEvent, + pub diff_stage_lines: KeyEvent, + pub diff_reset_lines: KeyEvent, + pub stashing_save: KeyEvent, + pub stashing_toggle_untracked: KeyEvent, + pub stashing_toggle_index: KeyEvent, + pub stash_apply: KeyEvent, + pub stash_open: KeyEvent, + pub stash_drop: KeyEvent, + pub cmd_bar_toggle: KeyEvent, + pub log_tag_commit: KeyEvent, + pub log_mark_commit: KeyEvent, + pub commit_amend: KeyEvent, + pub copy: KeyEvent, + pub create_branch: KeyEvent, + pub rename_branch: KeyEvent, + pub select_branch: KeyEvent, + pub delete_branch: KeyEvent, + pub merge_branch: KeyEvent, + pub tags: KeyEvent, + pub delete_tag: KeyEvent, + pub select_tag: KeyEvent, + pub push: KeyEvent, + pub open_file_tree: KeyEvent, + pub force_push: KeyEvent, + pub pull: KeyEvent, + pub abort_merge: KeyEvent, + pub undo_commit: KeyEvent, } #[rustfmt::skip] @@ -158,150 +158,150 @@ impl Default for KeyConfig { } impl KeyConfig { - fn save(&self, file: PathBuf) -> Result<()> { - let mut file = File::create(file)?; - let data = to_string_pretty(self, PrettyConfig::default())?; - file.write_all(data.as_bytes())?; - Ok(()) - } + fn save(&self, file: PathBuf) -> Result<()> { + let mut file = File::create(file)?; + let data = to_string_pretty(self, PrettyConfig::default())?; + file.write_all(data.as_bytes())?; + Ok(()) + } - pub fn get_config_file() -> Result { - let app_home = get_app_config_path()?; - Ok(app_home.join("key_config.ron")) - } + pub fn get_config_file() -> Result { + let app_home = get_app_config_path()?; + Ok(app_home.join("key_config.ron")) + } - fn read_file(config_file: PathBuf) -> Result { - let mut f = File::open(config_file)?; - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer)?; - Ok(ron::de::from_bytes(&buffer)?) - } + fn read_file(config_file: PathBuf) -> Result { + let mut f = File::open(config_file)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + Ok(ron::de::from_bytes(&buffer)?) + } - pub fn init(file: PathBuf) -> Result { - if file.exists() { - match Self::read_file(file.clone()) { - Err(e) => { - let config_path = file.clone(); - let config_path_old = - format!("{}.old", file.to_string_lossy()); - fs::rename( - config_path.clone(), - config_path_old.clone(), - )?; + pub fn init(file: PathBuf) -> Result { + if file.exists() { + match Self::read_file(file.clone()) { + Err(e) => { + let config_path = file.clone(); + let config_path_old = + format!("{}.old", file.to_string_lossy()); + fs::rename( + config_path.clone(), + config_path_old.clone(), + )?; - Self::default().save(file)?; + Self::default().save(file)?; - Err(anyhow::anyhow!("{}\n Old file was renamed to {:?}.\n Defaults loaded and saved as {:?}", + Err(anyhow::anyhow!("{}\n Old file was renamed to {:?}.\n Defaults loaded and saved as {:?}", e,config_path_old,config_path.to_string_lossy())) - } - Ok(res) => Ok(res), - } - } else { - Self::default().save(file)?; - Ok(Self::default()) - } - } + } + Ok(res) => Ok(res), + } + } else { + Self::default().save(file)?; + Ok(Self::default()) + } + } - //TODO: make this configurable (https://github.com/extrawurst/gitui/issues/465) - #[allow(clippy::unused_self)] - const fn get_key_symbol(&self, k: KeyCode) -> &str { - match k { - KeyCode::Enter => "\u{23ce}", //⏎ - KeyCode::Left => "\u{2190}", //← - KeyCode::Right => "\u{2192}", //→ - KeyCode::Up => "\u{2191}", //↑ - KeyCode::Down => "\u{2193}", //↓ - KeyCode::Backspace => "\u{232b}", //⌫ - KeyCode::Home => "\u{2912}", //⤒ - KeyCode::End => "\u{2913}", //⤓ - KeyCode::PageUp => "\u{21de}", //⇞ - KeyCode::PageDown => "\u{21df}", //⇟ - KeyCode::Tab => "\u{21e5}", //⇥ - KeyCode::BackTab => "\u{21e4}", //⇤ - KeyCode::Delete => "\u{2326}", //⌦ - KeyCode::Insert => "\u{2380}", //⎀ - KeyCode::Esc => "\u{238b}", //⎋ - _ => "?", - } - } + //TODO: make this configurable (https://github.com/extrawurst/gitui/issues/465) + #[allow(clippy::unused_self)] + const fn get_key_symbol(&self, k: KeyCode) -> &str { + match k { + KeyCode::Enter => "\u{23ce}", //⏎ + KeyCode::Left => "\u{2190}", //← + KeyCode::Right => "\u{2192}", //→ + KeyCode::Up => "\u{2191}", //↑ + KeyCode::Down => "\u{2193}", //↓ + KeyCode::Backspace => "\u{232b}", //⌫ + KeyCode::Home => "\u{2912}", //⤒ + KeyCode::End => "\u{2913}", //⤓ + KeyCode::PageUp => "\u{21de}", //⇞ + KeyCode::PageDown => "\u{21df}", //⇟ + KeyCode::Tab => "\u{21e5}", //⇥ + KeyCode::BackTab => "\u{21e4}", //⇤ + KeyCode::Delete => "\u{2326}", //⌦ + KeyCode::Insert => "\u{2380}", //⎀ + KeyCode::Esc => "\u{238b}", //⎋ + _ => "?", + } + } - pub fn get_hint(&self, ev: KeyEvent) -> String { - match ev.code { - KeyCode::Down - | KeyCode::Up - | KeyCode::Right - | KeyCode::Left - | KeyCode::Enter - | KeyCode::Backspace - | KeyCode::Home - | KeyCode::End - | KeyCode::PageUp - | KeyCode::PageDown - | KeyCode::Tab - | KeyCode::BackTab - | KeyCode::Delete - | KeyCode::Insert - | KeyCode::Esc => { - format!( - "{}{}", - Self::get_modifier_hint(ev.modifiers), - self.get_key_symbol(ev.code) - ) - } - KeyCode::Char(c) => { - format!( - "{}{}", - Self::get_modifier_hint(ev.modifiers), - c - ) - } - KeyCode::F(u) => { - format!( - "{}F{}", - Self::get_modifier_hint(ev.modifiers), - u - ) - } - KeyCode::Null => Self::get_modifier_hint(ev.modifiers), - } - } + pub fn get_hint(&self, ev: KeyEvent) -> String { + match ev.code { + KeyCode::Down + | KeyCode::Up + | KeyCode::Right + | KeyCode::Left + | KeyCode::Enter + | KeyCode::Backspace + | KeyCode::Home + | KeyCode::End + | KeyCode::PageUp + | KeyCode::PageDown + | KeyCode::Tab + | KeyCode::BackTab + | KeyCode::Delete + | KeyCode::Insert + | KeyCode::Esc => { + format!( + "{}{}", + Self::get_modifier_hint(ev.modifiers), + self.get_key_symbol(ev.code) + ) + } + KeyCode::Char(c) => { + format!( + "{}{}", + Self::get_modifier_hint(ev.modifiers), + c + ) + } + KeyCode::F(u) => { + format!( + "{}F{}", + Self::get_modifier_hint(ev.modifiers), + u + ) + } + KeyCode::Null => Self::get_modifier_hint(ev.modifiers), + } + } - //TODO: make customizable (see https://github.com/extrawurst/gitui/issues/465) - fn get_modifier_hint(modifier: KeyModifiers) -> String { - match modifier { - KeyModifiers::CONTROL => "^".to_string(), - KeyModifiers::SHIFT => { - "\u{21e7}".to_string() //⇧ - } - KeyModifiers::ALT => { - "\u{2325}".to_string() //⌥ - } - _ => String::new(), - } - } + //TODO: make customizable (see https://github.com/extrawurst/gitui/issues/465) + fn get_modifier_hint(modifier: KeyModifiers) -> String { + match modifier { + KeyModifiers::CONTROL => "^".to_string(), + KeyModifiers::SHIFT => { + "\u{21e7}".to_string() //⇧ + } + KeyModifiers::ALT => { + "\u{2325}".to_string() //⌥ + } + _ => String::new(), + } + } } #[cfg(test)] mod tests { - use super::KeyConfig; - use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + use super::KeyConfig; + use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - #[test] - fn test_get_hint() { - let config = KeyConfig::default(); - let h = config.get_hint(KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - }); - assert_eq!(h, "^c"); - } + #[test] + fn test_get_hint() { + let config = KeyConfig::default(); + let h = config.get_hint(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + }); + assert_eq!(h, "^c"); + } - #[test] - fn test_load_vim_style_example() { - assert_eq!( - KeyConfig::read_file("vim_style_key_config.ron".into()) - .is_ok(), - true - ); - } + #[test] + fn test_load_vim_style_example() { + assert_eq!( + KeyConfig::read_file("vim_style_key_config.ron".into()) + .is_ok(), + true + ); + } } diff --git a/src/main.rs b/src/main.rs index ac17bfbd..6b0bdfab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ #![forbid(unsafe_code)] #![deny( - unused_imports, - unused_must_use, - dead_code, - unstable_name_collisions, - unused_assignments + unused_imports, + unused_must_use, + dead_code, + unstable_name_collisions, + unused_assignments )] #![deny(clippy::all, clippy::perf, clippy::nursery, clippy::pedantic)] #![deny(clippy::filetype_is_file)] @@ -41,11 +41,11 @@ use asyncgit::AsyncGitNotification; use backtrace::Backtrace; use crossbeam_channel::{tick, unbounded, Receiver, Select}; use crossterm::{ - terminal::{ - disable_raw_mode, enable_raw_mode, EnterAlternateScreen, - LeaveAlternateScreen, - }, - ExecutableCommand, + terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, + LeaveAlternateScreen, + }, + ExecutableCommand, }; use input::{Input, InputEvent, InputState}; use keys::KeyConfig; @@ -54,13 +54,13 @@ use scopeguard::defer; use scopetime::scope_time; use spinner::Spinner; use std::{ - io::{self, Write}, - panic, process, - time::{Duration, Instant}, + io::{self, Write}, + panic, process, + time::{Duration, Instant}, }; use tui::{ - backend::{Backend, CrosstermBackend}, - Terminal, + backend::{Backend, CrosstermBackend}, + Terminal, }; use ui::style::Theme; @@ -70,241 +70,241 @@ static SPINNER_INTERVAL: Duration = Duration::from_millis(80); /// #[derive(Clone, Copy)] pub enum QueueEvent { - Tick, - SpinnerUpdate, - AsyncEvent(AsyncNotification), - InputEvent(InputEvent), + Tick, + SpinnerUpdate, + AsyncEvent(AsyncNotification), + InputEvent(InputEvent), } #[derive(Clone, Copy, Debug, PartialEq)] pub enum AsyncAppNotification { - /// - SyntaxHighlighting, - /// - RemoteTags, + /// + SyntaxHighlighting, + /// + RemoteTags, } #[derive(Clone, Copy, Debug, PartialEq)] pub enum AsyncNotification { - /// - App(AsyncAppNotification), - /// - Git(AsyncGitNotification), + /// + App(AsyncAppNotification), + /// + Git(AsyncGitNotification), } fn main() -> Result<()> { - let cliargs = process_cmdline()?; + let cliargs = process_cmdline()?; - let _profiler = Profiler::new(); + let _profiler = Profiler::new(); - asyncgit::register_tracing_logging(); + asyncgit::register_tracing_logging(); - if !valid_path()? { - eprintln!("invalid path\nplease run gitui inside of a non-bare git repository"); - return Ok(()); - } + if !valid_path()? { + eprintln!("invalid path\nplease run gitui inside of a non-bare git repository"); + return Ok(()); + } - let key_config = KeyConfig::init(KeyConfig::get_config_file()?) - .map_err(|e| eprintln!("KeyConfig loading error: {}", e)) - .unwrap_or_default(); - let theme = Theme::init(cliargs.theme) - .map_err(|e| eprintln!("Theme loading error: {}", e)) - .unwrap_or_default(); + let key_config = KeyConfig::init(KeyConfig::get_config_file()?) + .map_err(|e| eprintln!("KeyConfig loading error: {}", e)) + .unwrap_or_default(); + let theme = Theme::init(cliargs.theme) + .map_err(|e| eprintln!("Theme loading error: {}", e)) + .unwrap_or_default(); - setup_terminal()?; - defer! { - shutdown_terminal(); - } + setup_terminal()?; + defer! { + shutdown_terminal(); + } - set_panic_handlers()?; + set_panic_handlers()?; - let mut terminal = start_terminal(io::stdout())?; + let mut terminal = start_terminal(io::stdout())?; - let (tx_git, rx_git) = unbounded(); - let (tx_app, rx_app) = unbounded(); + let (tx_git, rx_git) = unbounded(); + let (tx_app, rx_app) = unbounded(); - let input = Input::new(); + let input = Input::new(); - let rx_input = input.receiver(); - let ticker = tick(TICK_INTERVAL); - let spinner_ticker = tick(SPINNER_INTERVAL); + let rx_input = input.receiver(); + let ticker = tick(TICK_INTERVAL); + let spinner_ticker = tick(SPINNER_INTERVAL); - let mut app = - App::new(&tx_git, &tx_app, input, theme, key_config); + let mut app = + App::new(&tx_git, &tx_app, input, theme, key_config); - let mut spinner = Spinner::default(); - let mut first_update = true; + let mut spinner = Spinner::default(); + let mut first_update = true; - loop { - let event = if first_update { - first_update = false; - QueueEvent::Tick - } else { - select_event( - &rx_input, - &rx_git, - &rx_app, - &ticker, - &spinner_ticker, - )? - }; + loop { + let event = if first_update { + first_update = false; + QueueEvent::Tick + } else { + select_event( + &rx_input, + &rx_git, + &rx_app, + &ticker, + &spinner_ticker, + )? + }; - { - if let QueueEvent::SpinnerUpdate = event { - spinner.update(); - spinner.draw(&mut terminal)?; - continue; - } + { + if let QueueEvent::SpinnerUpdate = event { + spinner.update(); + spinner.draw(&mut terminal)?; + continue; + } - scope_time!("loop"); + scope_time!("loop"); - match event { - QueueEvent::InputEvent(ev) => { - if let InputEvent::State(InputState::Polling) = ev - { - //Note: external ed closed, we need to re-hide cursor - terminal.hide_cursor()?; - } - app.event(ev)?; - } - QueueEvent::Tick => app.update()?, - QueueEvent::AsyncEvent(ev) => { - if !matches!( - ev, - AsyncNotification::Git( - AsyncGitNotification::FinishUnchanged - ) - ) { - app.update_async(ev)?; - } - } - QueueEvent::SpinnerUpdate => unreachable!(), - } + match event { + QueueEvent::InputEvent(ev) => { + if let InputEvent::State(InputState::Polling) = ev + { + //Note: external ed closed, we need to re-hide cursor + terminal.hide_cursor()?; + } + app.event(ev)?; + } + QueueEvent::Tick => app.update()?, + QueueEvent::AsyncEvent(ev) => { + if !matches!( + ev, + AsyncNotification::Git( + AsyncGitNotification::FinishUnchanged + ) + ) { + app.update_async(ev)?; + } + } + QueueEvent::SpinnerUpdate => unreachable!(), + } - draw(&mut terminal, &app)?; + draw(&mut terminal, &app)?; - spinner.set_state(app.any_work_pending()); - spinner.draw(&mut terminal)?; + spinner.set_state(app.any_work_pending()); + spinner.draw(&mut terminal)?; - if app.is_quit() { - break; - } - } - } + if app.is_quit() { + break; + } + } + } - Ok(()) + Ok(()) } fn setup_terminal() -> Result<()> { - enable_raw_mode()?; - io::stdout().execute(EnterAlternateScreen)?; - Ok(()) + enable_raw_mode()?; + io::stdout().execute(EnterAlternateScreen)?; + Ok(()) } fn shutdown_terminal() { - let leave_screen = - io::stdout().execute(LeaveAlternateScreen).map(|_f| ()); + let leave_screen = + io::stdout().execute(LeaveAlternateScreen).map(|_f| ()); - if let Err(e) = leave_screen { - eprintln!("leave_screen failed:\n{}", e); - } + if let Err(e) = leave_screen { + eprintln!("leave_screen failed:\n{}", e); + } - let leave_raw_mode = disable_raw_mode(); + let leave_raw_mode = disable_raw_mode(); - if let Err(e) = leave_raw_mode { - eprintln!("leave_raw_mode failed:\n{}", e); - } + if let Err(e) = leave_raw_mode { + eprintln!("leave_raw_mode failed:\n{}", e); + } } fn draw( - terminal: &mut Terminal, - app: &App, + terminal: &mut Terminal, + app: &App, ) -> io::Result<()> { - if app.requires_redraw() { - terminal.resize(terminal.size()?)?; - } + if app.requires_redraw() { + terminal.resize(terminal.size()?)?; + } - terminal.draw(|mut f| { - if let Err(e) = app.draw(&mut f) { - log::error!("failed to draw: {:?}", e); - } - })?; + terminal.draw(|mut f| { + if let Err(e) = app.draw(&mut f) { + log::error!("failed to draw: {:?}", e); + } + })?; - Ok(()) + Ok(()) } fn valid_path() -> Result { - Ok(asyncgit::sync::is_repo(asyncgit::CWD) - && !asyncgit::sync::is_bare_repo(asyncgit::CWD)?) + Ok(asyncgit::sync::is_repo(asyncgit::CWD) + && !asyncgit::sync::is_bare_repo(asyncgit::CWD)?) } fn select_event( - rx_input: &Receiver, - rx_git: &Receiver, - rx_app: &Receiver, - rx_ticker: &Receiver, - rx_spinner: &Receiver, + rx_input: &Receiver, + rx_git: &Receiver, + rx_app: &Receiver, + rx_ticker: &Receiver, + rx_spinner: &Receiver, ) -> Result { - let mut sel = Select::new(); + let mut sel = Select::new(); - sel.recv(rx_input); - sel.recv(rx_git); - sel.recv(rx_app); - sel.recv(rx_ticker); - sel.recv(rx_spinner); + sel.recv(rx_input); + sel.recv(rx_git); + sel.recv(rx_app); + sel.recv(rx_ticker); + sel.recv(rx_spinner); - let oper = sel.select(); - let index = oper.index(); + let oper = sel.select(); + let index = oper.index(); - let ev = match index { - 0 => oper.recv(rx_input).map(QueueEvent::InputEvent), - 1 => oper.recv(rx_git).map(|e| { - QueueEvent::AsyncEvent(AsyncNotification::Git(e)) - }), - 2 => oper.recv(rx_app).map(|e| { - QueueEvent::AsyncEvent(AsyncNotification::App(e)) - }), - 3 => oper.recv(rx_ticker).map(|_| QueueEvent::Tick), - 4 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate), - _ => bail!("unknown select source"), - }?; + let ev = match index { + 0 => oper.recv(rx_input).map(QueueEvent::InputEvent), + 1 => oper.recv(rx_git).map(|e| { + QueueEvent::AsyncEvent(AsyncNotification::Git(e)) + }), + 2 => oper.recv(rx_app).map(|e| { + QueueEvent::AsyncEvent(AsyncNotification::App(e)) + }), + 3 => oper.recv(rx_ticker).map(|_| QueueEvent::Tick), + 4 => oper.recv(rx_spinner).map(|_| QueueEvent::SpinnerUpdate), + _ => bail!("unknown select source"), + }?; - Ok(ev) + Ok(ev) } fn start_terminal( - buf: W, + buf: W, ) -> io::Result>> { - let backend = CrosstermBackend::new(buf); - let mut terminal = Terminal::new(backend)?; - terminal.hide_cursor()?; - terminal.clear()?; + let backend = CrosstermBackend::new(buf); + let mut terminal = Terminal::new(backend)?; + terminal.hide_cursor()?; + terminal.clear()?; - Ok(terminal) + Ok(terminal) } fn set_panic_handlers() -> Result<()> { - // regular panic handler - panic::set_hook(Box::new(|e| { - let backtrace = Backtrace::new(); - //TODO: create macro to do both in one - log::error!("panic: {:?}\ntrace:\n{:?}", e, backtrace); - eprintln!("panic: {:?}\ntrace:\n{:?}", e, backtrace); - shutdown_terminal(); - })); + // regular panic handler + panic::set_hook(Box::new(|e| { + let backtrace = Backtrace::new(); + //TODO: create macro to do both in one + log::error!("panic: {:?}\ntrace:\n{:?}", e, backtrace); + eprintln!("panic: {:?}\ntrace:\n{:?}", e, backtrace); + shutdown_terminal(); + })); - // global threadpool - rayon_core::ThreadPoolBuilder::new() - .panic_handler(|e| { - let backtrace = Backtrace::new(); - //TODO: create macro to do both in one - log::error!("panic: {:?}\ntrace:\n{:?}", e, backtrace); - eprintln!("panic: {:?}\ntrace:\n{:?}", e, backtrace); - shutdown_terminal(); - process::abort(); - }) - .num_threads(4) - .build_global()?; + // global threadpool + rayon_core::ThreadPoolBuilder::new() + .panic_handler(|e| { + let backtrace = Backtrace::new(); + //TODO: create macro to do both in one + log::error!("panic: {:?}\ntrace:\n{:?}", e, backtrace); + eprintln!("panic: {:?}\ntrace:\n{:?}", e, backtrace); + shutdown_terminal(); + process::abort(); + }) + .num_threads(4) + .build_global()?; - Ok(()) + Ok(()) } diff --git a/src/notify_mutex.rs b/src/notify_mutex.rs index 5aaa5d54..bd5a031e 100644 --- a/src/notify_mutex.rs +++ b/src/notify_mutex.rs @@ -3,39 +3,39 @@ use std::sync::{Arc, Condvar, Mutex}; /// combines a `Mutex` and `Condvar` to allow waiting for a change in the variable protected by the `Mutex` #[derive(Clone, Debug)] pub struct NotifyableMutex { - data: Arc<(Mutex, Condvar)>, + data: Arc<(Mutex, Condvar)>, } impl NotifyableMutex { - /// - pub fn new(start_value: T) -> Self { - Self { - data: Arc::new((Mutex::new(start_value), Condvar::new())), - } - } + /// + pub fn new(start_value: T) -> Self { + Self { + data: Arc::new((Mutex::new(start_value), Condvar::new())), + } + } - /// - pub fn wait(&self, condition: T) - where - T: PartialEq + Copy, - { - let mut data = self.data.0.lock().expect("lock err"); - while *data != condition { - data = self.data.1.wait(data).expect("wait err"); - } - } + /// + pub fn wait(&self, condition: T) + where + T: PartialEq + Copy, + { + let mut data = self.data.0.lock().expect("lock err"); + while *data != condition { + data = self.data.1.wait(data).expect("wait err"); + } + } - /// - pub fn set_and_notify(&self, value: T) { - *self.data.0.lock().expect("set err") = value; - self.data.1.notify_one(); - } + /// + pub fn set_and_notify(&self, value: T) { + *self.data.0.lock().expect("set err") = value; + self.data.1.notify_one(); + } - /// - pub fn get(&self) -> T - where - T: Copy, - { - *self.data.0.lock().expect("get err") - } + /// + pub fn get(&self) -> T + where + T: Copy, + { + *self.data.0.lock().expect("get err") + } } diff --git a/src/profiler.rs b/src/profiler.rs index 7527839a..9543db3c 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -1,39 +1,39 @@ /// helper struct to not pollute main with feature flags shenanigans for the profiler /// also we make sure to generate a flamegraph on program exit pub struct Profiler { - #[cfg(feature = "pprof")] - #[cfg(not(windows))] - guard: pprof::ProfilerGuard<'static>, + #[cfg(feature = "pprof")] + #[cfg(not(windows))] + guard: pprof::ProfilerGuard<'static>, } impl Profiler { - #[allow(clippy::missing_const_for_fn)] - pub fn new() -> Self { - Self { - #[cfg(feature = "pprof")] - #[cfg(not(windows))] - guard: pprof::ProfilerGuard::new(100) - .expect("profiler launch error"), - } - } + #[allow(clippy::missing_const_for_fn)] + pub fn new() -> Self { + Self { + #[cfg(feature = "pprof")] + #[cfg(not(windows))] + guard: pprof::ProfilerGuard::new(100) + .expect("profiler launch error"), + } + } - #[allow(clippy::unused_self)] - fn report(&mut self) { - #[cfg(feature = "pprof")] - #[cfg(not(windows))] - if let Ok(report) = self.guard.report().build() { - let file = std::fs::File::create("flamegraph.svg") - .expect("flamegraph file err"); + #[allow(clippy::unused_self)] + fn report(&mut self) { + #[cfg(feature = "pprof")] + #[cfg(not(windows))] + if let Ok(report) = self.guard.report().build() { + let file = std::fs::File::create("flamegraph.svg") + .expect("flamegraph file err"); - report.flamegraph(&file).expect("flamegraph write err"); + report.flamegraph(&file).expect("flamegraph write err"); - log::info!("profiler reported"); - } - } + log::info!("profiler reported"); + } + } } impl Drop for Profiler { - fn drop(&mut self) { - self.report(); - } + fn drop(&mut self) { + self.report(); + } } diff --git a/src/queue.rs b/src/queue.rs index 6c781e2b..cac4ab9e 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -4,109 +4,109 @@ use bitflags::bitflags; use std::{cell::RefCell, collections::VecDeque, rc::Rc}; bitflags! { - /// flags defining what part of the app need to update - pub struct NeedsUpdate: u32 { - /// app::update - const ALL = 0b001; - /// diff may have changed (app::update_diff) - const DIFF = 0b010; - /// commands might need updating (app::update_commands) - const COMMANDS = 0b100; - /// branches have changed - const BRANCHES = 0b1000; - } + /// flags defining what part of the app need to update + pub struct NeedsUpdate: u32 { + /// app::update + const ALL = 0b001; + /// diff may have changed (app::update_diff) + const DIFF = 0b010; + /// commands might need updating (app::update_commands) + const COMMANDS = 0b100; + /// branches have changed + const BRANCHES = 0b1000; + } } /// data of item that is supposed to be reset pub struct ResetItem { - /// path to the item (folder/file) - pub path: String, - /// are talking about a folder here? otherwise it's a single file - pub is_folder: bool, + /// path to the item (folder/file) + pub path: String, + /// are talking about a folder here? otherwise it's a single file + pub is_folder: bool, } /// pub enum Action { - Reset(ResetItem), - ResetHunk(String, u64), - ResetLines(String, Vec), - StashDrop(CommitId), - StashPop(CommitId), - DeleteBranch(String, bool), - DeleteTag(String), - ForcePush(String, bool), - PullMerge { incoming: usize, rebase: bool }, - AbortMerge, + Reset(ResetItem), + ResetHunk(String, u64), + ResetLines(String, Vec), + StashDrop(CommitId), + StashPop(CommitId), + DeleteBranch(String, bool), + DeleteTag(String), + ForcePush(String, bool), + PullMerge { incoming: usize, rebase: bool }, + AbortMerge, } /// pub enum InternalEvent { - /// - ConfirmAction(Action), - /// - ConfirmedAction(Action), - /// - ShowErrorMsg(String), - /// - Update(NeedsUpdate), - /// - StatusLastFileMoved, - /// open commit msg input - OpenCommit, - /// - PopupStashing(StashingOptions), - /// - TabSwitch, - /// - InspectCommit(CommitId, Option), - /// - SelectCommitInRevlog(CommitId), - /// - TagCommit(CommitId), - /// - Tags, - /// - BlameFile(String), - /// - CreateBranch, - /// - RenameBranch(String, String), - /// - SelectBranch, - /// - OpenExternalEditor(Option), - /// - Push(String, bool, bool), - /// - Pull(String), - /// - PushTags, - /// - OpenFileTree(CommitId), + /// + ConfirmAction(Action), + /// + ConfirmedAction(Action), + /// + ShowErrorMsg(String), + /// + Update(NeedsUpdate), + /// + StatusLastFileMoved, + /// open commit msg input + OpenCommit, + /// + PopupStashing(StashingOptions), + /// + TabSwitch, + /// + InspectCommit(CommitId, Option), + /// + SelectCommitInRevlog(CommitId), + /// + TagCommit(CommitId), + /// + Tags, + /// + BlameFile(String), + /// + CreateBranch, + /// + RenameBranch(String, String), + /// + SelectBranch, + /// + OpenExternalEditor(Option), + /// + Push(String, bool, bool), + /// + Pull(String), + /// + PushTags, + /// + OpenFileTree(CommitId), } /// single threaded simple queue for components to communicate with each other #[derive(Clone)] pub struct Queue { - data: Rc>>, + data: Rc>>, } impl Queue { - pub fn new() -> Self { - Self { - data: Rc::new(RefCell::new(VecDeque::new())), - } - } + pub fn new() -> Self { + Self { + data: Rc::new(RefCell::new(VecDeque::new())), + } + } - pub fn push(&self, ev: InternalEvent) { - self.data.borrow_mut().push_back(ev); - } + pub fn push(&self, ev: InternalEvent) { + self.data.borrow_mut().push_back(ev); + } - pub fn pop(&self) -> Option { - self.data.borrow_mut().pop_front() - } + pub fn pop(&self) -> Option { + self.data.borrow_mut().pop_front() + } - pub fn clear(&self) { - self.data.borrow_mut().clear(); - } + pub fn clear(&self) { + self.data.borrow_mut().clear(); + } } diff --git a/src/spinner.rs b/src/spinner.rs index 0f436a0f..6c561c3c 100644 --- a/src/spinner.rs +++ b/src/spinner.rs @@ -4,61 +4,61 @@ use tui::{backend::Backend, Terminal}; // static SPINNER_CHARS: &[char] = &['◢', '◣', '◤', '◥']; // static SPINNER_CHARS: &[char] = &['⢹', '⢺', '⢼', '⣸', '⣇', '⡧', '⡗', '⡏']; static SPINNER_CHARS: &[char] = - &['⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽', '⣾']; + &['⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽', '⣾']; /// pub struct Spinner { - idx: usize, - active: bool, - last_char: Cell, + idx: usize, + active: bool, + last_char: Cell, } impl Default for Spinner { - fn default() -> Self { - Self { - idx: 0, - active: false, - last_char: Cell::new(' '), - } - } + fn default() -> Self { + Self { + idx: 0, + active: false, + last_char: Cell::new(' '), + } + } } impl Spinner { - /// increment spinner graphic by one - pub fn update(&mut self) { - self.idx += 1; - self.idx %= SPINNER_CHARS.len(); - } + /// increment spinner graphic by one + pub fn update(&mut self) { + self.idx += 1; + self.idx %= SPINNER_CHARS.len(); + } - /// - pub fn set_state(&mut self, active: bool) { - self.active = active; - } + /// + pub fn set_state(&mut self, active: bool) { + self.active = active; + } - /// draws or removes spinner char depending on `pending` state - pub fn draw( - &self, - terminal: &mut Terminal, - ) -> io::Result<()> { - let idx = self.idx; + /// draws or removes spinner char depending on `pending` state + pub fn draw( + &self, + terminal: &mut Terminal, + ) -> io::Result<()> { + let idx = self.idx; - let char_to_draw = - if self.active { SPINNER_CHARS[idx] } else { ' ' }; + let char_to_draw = + if self.active { SPINNER_CHARS[idx] } else { ' ' }; - if self.last_char.get() != char_to_draw { - self.last_char.set(char_to_draw); + if self.last_char.get() != char_to_draw { + self.last_char.set(char_to_draw); - let c = tui::buffer::Cell::default() - .set_char(char_to_draw) - .clone(); + let c = tui::buffer::Cell::default() + .set_char(char_to_draw) + .clone(); - terminal - .backend_mut() - .draw(vec![(0_u16, 0_u16, &c)].into_iter())?; + terminal + .backend_mut() + .draw(vec![(0_u16, 0_u16, &c)].into_iter())?; - tui::backend::Backend::flush(terminal.backend_mut())?; - } + tui::backend::Backend::flush(terminal.backend_mut())?; + } - Ok(()) - } + Ok(()) + } } diff --git a/src/strings.rs b/src/strings.rs index 566fa24d..949cbed2 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -1,8 +1,8 @@ use crate::keys::SharedKeyConfig; pub mod order { - pub static NAV: i8 = 2; - pub static RARE_ACTION: i8 = 1; + pub static NAV: i8 = 2; + pub static RARE_ACTION: i8 = 1; } pub static PUSH_POPUP_MSG: &str = "Push"; @@ -21,1095 +21,1095 @@ pub static PUSH_TAGS_STATES_PUSHING: &str = "pushing"; pub static PUSH_TAGS_STATES_DONE: &str = "done"; pub fn title_branches() -> String { - "Branches".to_string() + "Branches".to_string() } pub fn title_tags() -> String { - "Tags".to_string() + "Tags".to_string() } pub fn title_status(_key_config: &SharedKeyConfig) -> String { - "Unstaged Changes".to_string() + "Unstaged Changes".to_string() } pub fn title_diff(_key_config: &SharedKeyConfig) -> String { - "Diff: ".to_string() + "Diff: ".to_string() } pub fn title_index(_key_config: &SharedKeyConfig) -> String { - "Staged Changes".to_string() + "Staged Changes".to_string() } pub fn tab_status(key_config: &SharedKeyConfig) -> String { - format!("Status [{}]", key_config.get_hint(key_config.tab_status)) + format!("Status [{}]", key_config.get_hint(key_config.tab_status)) } pub fn tab_log(key_config: &SharedKeyConfig) -> String { - format!("Log [{}]", key_config.get_hint(key_config.tab_log)) + format!("Log [{}]", key_config.get_hint(key_config.tab_log)) } pub fn tab_files(key_config: &SharedKeyConfig) -> String { - format!("Files [{}]", key_config.get_hint(key_config.tab_files)) + format!("Files [{}]", key_config.get_hint(key_config.tab_files)) } pub fn tab_stashing(key_config: &SharedKeyConfig) -> String { - format!( - "Stashing [{}]", - key_config.get_hint(key_config.tab_stashing) - ) + format!( + "Stashing [{}]", + key_config.get_hint(key_config.tab_stashing) + ) } pub fn tab_stashes(key_config: &SharedKeyConfig) -> String { - format!( - "Stashes [{}]", - key_config.get_hint(key_config.tab_stashes) - ) + format!( + "Stashes [{}]", + key_config.get_hint(key_config.tab_stashes) + ) } pub fn tab_divider(_key_config: &SharedKeyConfig) -> String { - " | ".to_string() + " | ".to_string() } pub fn cmd_splitter(_key_config: &SharedKeyConfig) -> String { - " ".to_string() + " ".to_string() } pub fn msg_opening_editor(_key_config: &SharedKeyConfig) -> String { - "opening editor...".to_string() + "opening editor...".to_string() } pub fn msg_title_error(_key_config: &SharedKeyConfig) -> String { - "Error".to_string() + "Error".to_string() } pub fn commit_title() -> String { - "Commit".to_string() + "Commit".to_string() } pub fn commit_title_merge() -> String { - "Commit (Merge)".to_string() + "Commit (Merge)".to_string() } pub fn commit_title_amend() -> String { - "Commit (Amend)".to_string() + "Commit (Amend)".to_string() } pub fn commit_msg(_key_config: &SharedKeyConfig) -> String { - "type commit message..".to_string() + "type commit message..".to_string() } pub fn commit_first_line_warning(count: usize) -> String { - format!("[subject length: {}]", count) + format!("[subject length: {}]", count) } pub const fn branch_name_invalid() -> &'static str { - "[invalid name]" + "[invalid name]" } pub fn commit_editor_msg(_key_config: &SharedKeyConfig) -> String { - r##" + r##" # Edit your commit message # Lines starting with '#' will be ignored"## - .to_string() + .to_string() } pub fn stash_popup_title(_key_config: &SharedKeyConfig) -> String { - "Stash".to_string() + "Stash".to_string() } pub fn stash_popup_msg(_key_config: &SharedKeyConfig) -> String { - "type name (optional)".to_string() + "type name (optional)".to_string() } pub fn confirm_title_reset() -> String { - "Reset".to_string() + "Reset".to_string() } pub fn confirm_title_stashdrop( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Drop".to_string() + "Drop".to_string() } pub fn confirm_title_stashpop( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Pop".to_string() + "Pop".to_string() } pub fn confirm_title_merge( - _key_config: &SharedKeyConfig, - rebase: bool, + _key_config: &SharedKeyConfig, + rebase: bool, ) -> String { - if rebase { - "Merge (via rebase)".to_string() - } else { - "Merge (via commit)".to_string() - } + if rebase { + "Merge (via rebase)".to_string() + } else { + "Merge (via commit)".to_string() + } } pub fn confirm_msg_merge( - _key_config: &SharedKeyConfig, - incoming: usize, - rebase: bool, + _key_config: &SharedKeyConfig, + incoming: usize, + rebase: bool, ) -> String { - if rebase { - format!("Rebase onto {} incoming commits?", incoming) - } else { - format!("Merge of {} incoming commits?", incoming) - } + if rebase { + format!("Rebase onto {} incoming commits?", incoming) + } else { + format!("Merge of {} incoming commits?", incoming) + } } pub fn confirm_title_abortmerge() -> String { - "Abort merge?".to_string() + "Abort merge?".to_string() } pub fn confirm_msg_abortmerge() -> String { - "This will revert all uncommitted changes. Are you sure?" - .to_string() + "This will revert all uncommitted changes. Are you sure?" + .to_string() } pub fn confirm_msg_reset() -> String { - "confirm file reset?".to_string() + "confirm file reset?".to_string() } pub fn confirm_msg_reset_lines(lines: usize) -> String { - format!( - "are you sure you want to discard {} selected lines?", - lines - ) + format!( + "are you sure you want to discard {} selected lines?", + lines + ) } pub fn confirm_msg_stashdrop( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "confirm stash drop?".to_string() + "confirm stash drop?".to_string() } pub fn confirm_msg_stashpop(_key_config: &SharedKeyConfig) -> String { - "The stash will be applied and removed from the stash list. Confirm stash pop?" + "The stash will be applied and removed from the stash list. Confirm stash pop?" .to_string() } pub fn confirm_msg_resethunk( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "confirm reset hunk?".to_string() + "confirm reset hunk?".to_string() } pub fn confirm_title_delete_branch( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Delete Branch".to_string() + "Delete Branch".to_string() } pub fn confirm_msg_delete_branch( - _key_config: &SharedKeyConfig, - branch_ref: &str, + _key_config: &SharedKeyConfig, + branch_ref: &str, ) -> String { - format!("Confirm deleting branch: '{}' ?", branch_ref) + format!("Confirm deleting branch: '{}' ?", branch_ref) } pub fn confirm_title_delete_remote_branch( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Delete Remote Branch".to_string() + "Delete Remote Branch".to_string() } pub fn confirm_msg_delete_remote_branch( - _key_config: &SharedKeyConfig, - branch_ref: &str, + _key_config: &SharedKeyConfig, + branch_ref: &str, ) -> String { - format!("Confirm deleting remote branch: '{}' ?", branch_ref) + format!("Confirm deleting remote branch: '{}' ?", branch_ref) } pub fn confirm_title_delete_tag( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Delete Tag".to_string() + "Delete Tag".to_string() } pub fn confirm_msg_delete_tag( - _key_config: &SharedKeyConfig, - tag_name: &str, + _key_config: &SharedKeyConfig, + tag_name: &str, ) -> String { - format!("Confirm deleting Tag: '{}' ?", tag_name) + format!("Confirm deleting Tag: '{}' ?", tag_name) } pub fn confirm_title_force_push( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Force Push".to_string() + "Force Push".to_string() } pub fn confirm_msg_force_push( - _key_config: &SharedKeyConfig, - branch_ref: &str, + _key_config: &SharedKeyConfig, + branch_ref: &str, ) -> String { - format!( + format!( "Confirm force push to branch '{}' ? This may rewrite history.", branch_ref ) } pub fn log_title(_key_config: &SharedKeyConfig) -> String { - "Commit".to_string() + "Commit".to_string() } pub fn blame_title(_key_config: &SharedKeyConfig) -> String { - "Blame".to_string() + "Blame".to_string() } pub fn tag_commit_popup_title( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Tag".to_string() + "Tag".to_string() } pub fn tag_commit_popup_msg(_key_config: &SharedKeyConfig) -> String { - "type tag".to_string() + "type tag".to_string() } pub fn stashlist_title(_key_config: &SharedKeyConfig) -> String { - "Stashes".to_string() + "Stashes".to_string() } pub fn help_title(_key_config: &SharedKeyConfig) -> String { - "Help: all commands".to_string() + "Help: all commands".to_string() } pub fn stashing_files_title(_key_config: &SharedKeyConfig) -> String { - "Files to Stash".to_string() + "Files to Stash".to_string() } pub fn stashing_options_title( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Options".to_string() + "Options".to_string() } pub fn loading_text(_key_config: &SharedKeyConfig) -> String { - "Loading ...".to_string() + "Loading ...".to_string() } pub fn create_branch_popup_title( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Branch".to_string() + "Branch".to_string() } pub fn create_branch_popup_msg( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "type branch name".to_string() + "type branch name".to_string() } pub fn username_popup_title(_key_config: &SharedKeyConfig) -> String { - "Username".to_string() + "Username".to_string() } pub fn username_popup_msg(_key_config: &SharedKeyConfig) -> String { - "type username".to_string() + "type username".to_string() } pub fn password_popup_title(_key_config: &SharedKeyConfig) -> String { - "Password".to_string() + "Password".to_string() } pub fn password_popup_msg(_key_config: &SharedKeyConfig) -> String { - "type password".to_string() + "type password".to_string() } pub fn rename_branch_popup_title( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "Rename Branch".to_string() + "Rename Branch".to_string() } pub fn rename_branch_popup_msg( - _key_config: &SharedKeyConfig, + _key_config: &SharedKeyConfig, ) -> String { - "new branch name".to_string() + "new branch name".to_string() } pub mod commit { - use crate::keys::SharedKeyConfig; - pub fn details_author(_key_config: &SharedKeyConfig) -> String { - "Author: ".to_string() - } - pub fn details_committer( - _key_config: &SharedKeyConfig, - ) -> String { - "Committer: ".to_string() - } - pub fn details_sha(_key_config: &SharedKeyConfig) -> String { - "Sha: ".to_string() - } - pub fn details_date(_key_config: &SharedKeyConfig) -> String { - "Date: ".to_string() - } - pub fn details_tags(_key_config: &SharedKeyConfig) -> String { - "Tags: ".to_string() - } - pub fn details_info_title( - _key_config: &SharedKeyConfig, - ) -> String { - "Info".to_string() - } - pub fn details_message_title( - _key_config: &SharedKeyConfig, - ) -> String { - "Message".to_string() - } - pub fn details_files_title( - _key_config: &SharedKeyConfig, - ) -> String { - "Files:".to_string() - } + use crate::keys::SharedKeyConfig; + pub fn details_author(_key_config: &SharedKeyConfig) -> String { + "Author: ".to_string() + } + pub fn details_committer( + _key_config: &SharedKeyConfig, + ) -> String { + "Committer: ".to_string() + } + pub fn details_sha(_key_config: &SharedKeyConfig) -> String { + "Sha: ".to_string() + } + pub fn details_date(_key_config: &SharedKeyConfig) -> String { + "Date: ".to_string() + } + pub fn details_tags(_key_config: &SharedKeyConfig) -> String { + "Tags: ".to_string() + } + pub fn details_info_title( + _key_config: &SharedKeyConfig, + ) -> String { + "Info".to_string() + } + pub fn details_message_title( + _key_config: &SharedKeyConfig, + ) -> String { + "Message".to_string() + } + pub fn details_files_title( + _key_config: &SharedKeyConfig, + ) -> String { + "Files:".to_string() + } } pub mod commands { - use crate::components::CommandText; - use crate::keys::SharedKeyConfig; + use crate::components::CommandText; + use crate::keys::SharedKeyConfig; - static CMD_GROUP_GENERAL: &str = "-- General --"; - static CMD_GROUP_DIFF: &str = "-- Diff --"; - static CMD_GROUP_CHANGES: &str = "-- Changes --"; - static CMD_GROUP_COMMIT: &str = "-- Commit --"; - static CMD_GROUP_STASHING: &str = "-- Stashing --"; - static CMD_GROUP_STASHES: &str = "-- Stashes --"; - static CMD_GROUP_LOG: &str = "-- Log --"; - static CMD_GROUP_BRANCHES: &str = "-- Branches --"; + static CMD_GROUP_GENERAL: &str = "-- General --"; + static CMD_GROUP_DIFF: &str = "-- Diff --"; + static CMD_GROUP_CHANGES: &str = "-- Changes --"; + static CMD_GROUP_COMMIT: &str = "-- Commit --"; + static CMD_GROUP_STASHING: &str = "-- Stashing --"; + static CMD_GROUP_STASHES: &str = "-- Stashes --"; + static CMD_GROUP_LOG: &str = "-- Log --"; + static CMD_GROUP_BRANCHES: &str = "-- Branches --"; - pub fn toggle_tabs(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Next [{}]", - key_config.get_hint(key_config.tab_toggle) - ), - "switch to next tab", - CMD_GROUP_GENERAL, - ) - } - pub fn toggle_tabs_direct( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Tab [{}{}{}{}{}]", - key_config.get_hint(key_config.tab_status), - key_config.get_hint(key_config.tab_log), - key_config.get_hint(key_config.tab_files), - key_config.get_hint(key_config.tab_stashing), - key_config.get_hint(key_config.tab_stashes), - ), - "switch top level tabs directly", - CMD_GROUP_GENERAL, - ) - } - pub fn help_open(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Help [{}]", - key_config.get_hint(key_config.open_help) - ), - "open this help screen", - CMD_GROUP_GENERAL, - ) - } - pub fn navigate_commit_message( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Nav [{}{}]", - key_config.get_hint(key_config.move_up), - key_config.get_hint(key_config.move_down) - ), - "navigate commit message", - CMD_GROUP_GENERAL, - ) - } - pub fn navigate_tree( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Nav [{}{}{}{}]", - key_config.get_hint(key_config.move_up), - key_config.get_hint(key_config.move_down), - key_config.get_hint(key_config.move_right), - key_config.get_hint(key_config.move_left) - ), - "navigate tree view, collapse, expand", - CMD_GROUP_GENERAL, - ) - } - pub fn scroll(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Scroll [{}{}]", - key_config.get_hint(key_config.focus_above), - key_config.get_hint(key_config.focus_below) - ), - "scroll up or down in focused view", - CMD_GROUP_GENERAL, - ) - } - pub fn copy(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Copy [{}]", - key_config.get_hint(key_config.copy), - ), - "copy selected lines to clipboard", - CMD_GROUP_DIFF, - ) - } - pub fn copy_hash(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Copy Hash [{}]", - key_config.get_hint(key_config.copy), - ), - "copy selected commit hash to clipboard", - CMD_GROUP_LOG, - ) - } - pub fn push_tags(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Push Tags [{}]", - key_config.get_hint(key_config.push), - ), - "push tags to remote", - CMD_GROUP_LOG, - ) - } - pub fn diff_home_end( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Jump up/down [{},{},{},{}]", - key_config.get_hint(key_config.home), - key_config.get_hint(key_config.end), - key_config.get_hint(key_config.move_up), - key_config.get_hint(key_config.move_down) - ), - "scroll to top or bottom of diff", - CMD_GROUP_DIFF, - ) - } - pub fn diff_hunk_add( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Add hunk [{}]", - key_config.get_hint(key_config.enter), - ), - "adds selected hunk to stage", - CMD_GROUP_DIFF, - ) - } - pub fn diff_hunk_revert( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Reset hunk [{}]", - key_config.get_hint(key_config.status_reset_item), - ), - "reverts selected hunk", - CMD_GROUP_DIFF, - ) - } - pub fn diff_lines_revert( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Reset lines [{}]", - key_config.get_hint(key_config.diff_reset_lines), - ), - "resets selected lines", - CMD_GROUP_DIFF, - ) - } - pub fn diff_lines_stage( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Stage lines [{}]", - key_config.get_hint(key_config.diff_stage_lines), - ), - "stage selected lines", - CMD_GROUP_DIFF, - ) - } - pub fn diff_lines_unstage( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Unstage lines [{}]", - key_config.get_hint(key_config.diff_stage_lines), - ), - "unstage selected lines", - CMD_GROUP_DIFF, - ) - } - pub fn diff_hunk_remove( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Remove hunk [{}]", - key_config.get_hint(key_config.enter), - ), - "removes selected hunk from stage", - CMD_GROUP_DIFF, - ) - } - pub fn close_popup(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Close [{}]", - key_config.get_hint(key_config.exit_popup), - ), - "close overlay (e.g commit, help)", - CMD_GROUP_GENERAL, - ) - } - pub fn close_msg(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Close [{}]", - key_config.get_hint(key_config.enter), - ), - "close msg popup (e.g msg)", - CMD_GROUP_GENERAL, - ) - .hide_help() - } - pub fn validate_msg(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Validate [{}]", - key_config.get_hint(key_config.enter), - ), - "validate msg", - CMD_GROUP_GENERAL, - ) - .hide_help() - } - pub fn select_status( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "To files [{},{}]", - key_config.get_hint(key_config.tab_status), - key_config.get_hint(key_config.tab_log), - ), - "focus/select file tree of staged or unstaged files", - CMD_GROUP_GENERAL, - ) - } - pub fn abort_merge(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Abort merge [{}]", - key_config.get_hint(key_config.abort_merge), - ), - "abort ongoing merge", - CMD_GROUP_GENERAL, - ) - } - pub fn select_staging( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "To stage [{}]", - key_config.get_hint(key_config.toggle_workarea), - ), - "focus/select staging area", - CMD_GROUP_GENERAL, - ) - } - pub fn select_unstaged( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "To unstaged [{}]", - key_config.get_hint(key_config.toggle_workarea), - ), - "focus/select unstaged area", - CMD_GROUP_GENERAL, - ) - } - pub fn undo_commit(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Undo Commit [{}]", - key_config.get_hint(key_config.undo_commit), - ), - "undo last commit", - CMD_GROUP_GENERAL, - ) - } - pub fn commit_open(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Commit [{}]", - key_config.get_hint(key_config.open_commit), - ), - "open commit popup (available in non-empty stage)", - CMD_GROUP_COMMIT, - ) - } - pub fn commit_open_editor( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Open editor [{}]", - key_config.get_hint(key_config.open_commit_editor), - ), - "open commit editor (available in commit popup)", - CMD_GROUP_COMMIT, - ) - } - pub fn commit_enter(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Commit [{}]", - key_config.get_hint(key_config.enter), - ), - "commit (available when commit message is non-empty)", - CMD_GROUP_COMMIT, - ) - .hide_help() - } - pub fn commit_amend(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Amend [{}]", - key_config.get_hint(key_config.commit_amend), - ), - "amend last commit (available in commit popup)", - CMD_GROUP_COMMIT, - ) - } - pub fn edit_item(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Edit [{}]", - key_config.get_hint(key_config.edit_file), - ), - "edit the currently selected file in an external editor", - CMD_GROUP_CHANGES, - ) - } - pub fn stage_item(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Stage [{}]", - key_config.get_hint(key_config.enter), - ), - "stage currently selected file or entire path", - CMD_GROUP_CHANGES, - ) - } - pub fn stage_all(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Stage All [{}]", - key_config.get_hint(key_config.status_stage_all), - ), - "stage all changes (in unstaged files)", - CMD_GROUP_CHANGES, - ) - } - pub fn unstage_item(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Unstage [{}]", - key_config.get_hint(key_config.enter), - ), - "unstage currently selected file or entire path", - CMD_GROUP_CHANGES, - ) - } - pub fn unstage_all(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Unstage all [{}]", - key_config.get_hint(key_config.status_stage_all), - ), - "unstage all files (in staged files)", - CMD_GROUP_CHANGES, - ) - } - pub fn reset_item(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Reset [{}]", - key_config.get_hint(key_config.status_reset_item), - ), - "revert changes in selected file or entire path", - CMD_GROUP_CHANGES, - ) - } - pub fn ignore_item(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Ignore [{}]", - key_config.get_hint(key_config.status_ignore_file), - ), - "Add file or path to .gitignore", - CMD_GROUP_CHANGES, - ) - } + pub fn toggle_tabs(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Next [{}]", + key_config.get_hint(key_config.tab_toggle) + ), + "switch to next tab", + CMD_GROUP_GENERAL, + ) + } + pub fn toggle_tabs_direct( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Tab [{}{}{}{}{}]", + key_config.get_hint(key_config.tab_status), + key_config.get_hint(key_config.tab_log), + key_config.get_hint(key_config.tab_files), + key_config.get_hint(key_config.tab_stashing), + key_config.get_hint(key_config.tab_stashes), + ), + "switch top level tabs directly", + CMD_GROUP_GENERAL, + ) + } + pub fn help_open(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Help [{}]", + key_config.get_hint(key_config.open_help) + ), + "open this help screen", + CMD_GROUP_GENERAL, + ) + } + pub fn navigate_commit_message( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Nav [{}{}]", + key_config.get_hint(key_config.move_up), + key_config.get_hint(key_config.move_down) + ), + "navigate commit message", + CMD_GROUP_GENERAL, + ) + } + pub fn navigate_tree( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Nav [{}{}{}{}]", + key_config.get_hint(key_config.move_up), + key_config.get_hint(key_config.move_down), + key_config.get_hint(key_config.move_right), + key_config.get_hint(key_config.move_left) + ), + "navigate tree view, collapse, expand", + CMD_GROUP_GENERAL, + ) + } + pub fn scroll(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Scroll [{}{}]", + key_config.get_hint(key_config.focus_above), + key_config.get_hint(key_config.focus_below) + ), + "scroll up or down in focused view", + CMD_GROUP_GENERAL, + ) + } + pub fn copy(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Copy [{}]", + key_config.get_hint(key_config.copy), + ), + "copy selected lines to clipboard", + CMD_GROUP_DIFF, + ) + } + pub fn copy_hash(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Copy Hash [{}]", + key_config.get_hint(key_config.copy), + ), + "copy selected commit hash to clipboard", + CMD_GROUP_LOG, + ) + } + pub fn push_tags(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Push Tags [{}]", + key_config.get_hint(key_config.push), + ), + "push tags to remote", + CMD_GROUP_LOG, + ) + } + pub fn diff_home_end( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Jump up/down [{},{},{},{}]", + key_config.get_hint(key_config.home), + key_config.get_hint(key_config.end), + key_config.get_hint(key_config.move_up), + key_config.get_hint(key_config.move_down) + ), + "scroll to top or bottom of diff", + CMD_GROUP_DIFF, + ) + } + pub fn diff_hunk_add( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Add hunk [{}]", + key_config.get_hint(key_config.enter), + ), + "adds selected hunk to stage", + CMD_GROUP_DIFF, + ) + } + pub fn diff_hunk_revert( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Reset hunk [{}]", + key_config.get_hint(key_config.status_reset_item), + ), + "reverts selected hunk", + CMD_GROUP_DIFF, + ) + } + pub fn diff_lines_revert( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Reset lines [{}]", + key_config.get_hint(key_config.diff_reset_lines), + ), + "resets selected lines", + CMD_GROUP_DIFF, + ) + } + pub fn diff_lines_stage( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Stage lines [{}]", + key_config.get_hint(key_config.diff_stage_lines), + ), + "stage selected lines", + CMD_GROUP_DIFF, + ) + } + pub fn diff_lines_unstage( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Unstage lines [{}]", + key_config.get_hint(key_config.diff_stage_lines), + ), + "unstage selected lines", + CMD_GROUP_DIFF, + ) + } + pub fn diff_hunk_remove( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Remove hunk [{}]", + key_config.get_hint(key_config.enter), + ), + "removes selected hunk from stage", + CMD_GROUP_DIFF, + ) + } + pub fn close_popup(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Close [{}]", + key_config.get_hint(key_config.exit_popup), + ), + "close overlay (e.g commit, help)", + CMD_GROUP_GENERAL, + ) + } + pub fn close_msg(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Close [{}]", + key_config.get_hint(key_config.enter), + ), + "close msg popup (e.g msg)", + CMD_GROUP_GENERAL, + ) + .hide_help() + } + pub fn validate_msg(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Validate [{}]", + key_config.get_hint(key_config.enter), + ), + "validate msg", + CMD_GROUP_GENERAL, + ) + .hide_help() + } + pub fn select_status( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "To files [{},{}]", + key_config.get_hint(key_config.tab_status), + key_config.get_hint(key_config.tab_log), + ), + "focus/select file tree of staged or unstaged files", + CMD_GROUP_GENERAL, + ) + } + pub fn abort_merge(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Abort merge [{}]", + key_config.get_hint(key_config.abort_merge), + ), + "abort ongoing merge", + CMD_GROUP_GENERAL, + ) + } + pub fn select_staging( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "To stage [{}]", + key_config.get_hint(key_config.toggle_workarea), + ), + "focus/select staging area", + CMD_GROUP_GENERAL, + ) + } + pub fn select_unstaged( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "To unstaged [{}]", + key_config.get_hint(key_config.toggle_workarea), + ), + "focus/select unstaged area", + CMD_GROUP_GENERAL, + ) + } + pub fn undo_commit(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Undo Commit [{}]", + key_config.get_hint(key_config.undo_commit), + ), + "undo last commit", + CMD_GROUP_GENERAL, + ) + } + pub fn commit_open(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Commit [{}]", + key_config.get_hint(key_config.open_commit), + ), + "open commit popup (available in non-empty stage)", + CMD_GROUP_COMMIT, + ) + } + pub fn commit_open_editor( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Open editor [{}]", + key_config.get_hint(key_config.open_commit_editor), + ), + "open commit editor (available in commit popup)", + CMD_GROUP_COMMIT, + ) + } + pub fn commit_enter(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Commit [{}]", + key_config.get_hint(key_config.enter), + ), + "commit (available when commit message is non-empty)", + CMD_GROUP_COMMIT, + ) + .hide_help() + } + pub fn commit_amend(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Amend [{}]", + key_config.get_hint(key_config.commit_amend), + ), + "amend last commit (available in commit popup)", + CMD_GROUP_COMMIT, + ) + } + pub fn edit_item(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Edit [{}]", + key_config.get_hint(key_config.edit_file), + ), + "edit the currently selected file in an external editor", + CMD_GROUP_CHANGES, + ) + } + pub fn stage_item(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Stage [{}]", + key_config.get_hint(key_config.enter), + ), + "stage currently selected file or entire path", + CMD_GROUP_CHANGES, + ) + } + pub fn stage_all(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Stage All [{}]", + key_config.get_hint(key_config.status_stage_all), + ), + "stage all changes (in unstaged files)", + CMD_GROUP_CHANGES, + ) + } + pub fn unstage_item(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Unstage [{}]", + key_config.get_hint(key_config.enter), + ), + "unstage currently selected file or entire path", + CMD_GROUP_CHANGES, + ) + } + pub fn unstage_all(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Unstage all [{}]", + key_config.get_hint(key_config.status_stage_all), + ), + "unstage all files (in staged files)", + CMD_GROUP_CHANGES, + ) + } + pub fn reset_item(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Reset [{}]", + key_config.get_hint(key_config.status_reset_item), + ), + "revert changes in selected file or entire path", + CMD_GROUP_CHANGES, + ) + } + pub fn ignore_item(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Ignore [{}]", + key_config.get_hint(key_config.status_ignore_file), + ), + "Add file or path to .gitignore", + CMD_GROUP_CHANGES, + ) + } - pub fn diff_focus_left( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Back [{}]", - key_config.get_hint(key_config.focus_left), - ), - "view and select changed files", - CMD_GROUP_GENERAL, - ) - } - pub fn diff_focus_right( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Diff [{}]", - key_config.get_hint(key_config.focus_right), - ), - "inspect file diff", - CMD_GROUP_GENERAL, - ) - } - pub fn quit(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Quit [{}]", - key_config.get_hint(key_config.exit), - ), - "quit gitui application", - CMD_GROUP_GENERAL, - ) - } - pub fn confirm_action( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Confirm [{}]", - key_config.get_hint(key_config.enter), - ), - "confirm action", - CMD_GROUP_GENERAL, - ) - } - pub fn stashing_save( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Save [{}]", - key_config.get_hint(key_config.stashing_save), - ), - "opens stash name input popup", - CMD_GROUP_STASHING, - ) - } - pub fn stashing_toggle_indexed( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Toggle Staged [{}]", - key_config.get_hint(key_config.stashing_toggle_index), - ), - "toggle including staged files into stash", - CMD_GROUP_STASHING, - ) - } - pub fn stashing_toggle_untracked( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Toggle Untracked [{}]", - key_config - .get_hint(key_config.stashing_toggle_untracked), - ), - "toggle including untracked files into stash", - CMD_GROUP_STASHING, - ) - } - pub fn stashing_confirm_msg( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Stash [{}]", - key_config.get_hint(key_config.enter), - ), - "save files to stash", - CMD_GROUP_STASHING, - ) - } - pub fn stashlist_apply( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Apply [{}]", - key_config.get_hint(key_config.stash_apply), - ), - "apply selected stash", - CMD_GROUP_STASHES, - ) - } - pub fn stashlist_drop( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Drop [{}]", - key_config.get_hint(key_config.stash_drop), - ), - "drop selected stash", - CMD_GROUP_STASHES, - ) - } - pub fn stashlist_pop( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Pop [{}]", - key_config.get_hint(key_config.enter), - ), - "pop selected stash", - CMD_GROUP_STASHES, - ) - } - pub fn stashlist_inspect( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Inspect [{}]", - key_config.get_hint(key_config.focus_right), - ), - "open stash commit details (allows to diff files)", - CMD_GROUP_STASHES, - ) - } - pub fn log_details_toggle( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Details [{}]", - key_config.get_hint(key_config.enter), - ), - "open details of selected commit", - CMD_GROUP_LOG, - ) - } - pub fn log_details_open( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Inspect [{}]", - key_config.get_hint(key_config.focus_right), - ), - "inspect selected commit in detail", - CMD_GROUP_LOG, - ) - } - pub fn blame_file(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Blame [{}]", - key_config.get_hint(key_config.blame), - ), - "open blame view of selected file", - CMD_GROUP_LOG, - ) - } - pub fn log_tag_commit( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Tag [{}]", - key_config.get_hint(key_config.log_tag_commit), - ), - "tag commit", - CMD_GROUP_LOG, - ) - } - pub fn inspect_file_tree( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Files [{}]", - key_config.get_hint(key_config.open_file_tree), - ), - "inspect file tree at specific revision", - CMD_GROUP_LOG, - ) - } - pub fn tag_commit_confirm_msg( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Tag [{}]", - key_config.get_hint(key_config.enter), - ), - "tag commit", - CMD_GROUP_LOG, - ) - } - pub fn create_branch_confirm_msg( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Create Branch [{}]", - key_config.get_hint(key_config.enter), - ), - "create branch", - CMD_GROUP_BRANCHES, - ) - .hide_help() - } - pub fn open_branch_create_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Create [{}]", - key_config.get_hint(key_config.create_branch), - ), - "open create branch popup", - CMD_GROUP_BRANCHES, - ) - } - pub fn rename_branch_confirm_msg( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Rename Branch [{}]", - key_config.get_hint(key_config.enter), - ), - "rename branch", - CMD_GROUP_BRANCHES, - ) - .hide_help() - } - pub fn rename_branch_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Rename Branch [{}]", - key_config.get_hint(key_config.rename_branch), - ), - "rename branch", - CMD_GROUP_BRANCHES, - ) - } - pub fn delete_branch_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Delete [{}]", - key_config.get_hint(key_config.delete_branch), - ), - "delete a branch", - CMD_GROUP_BRANCHES, - ) - } - pub fn merge_branch_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Merge [{}]", - key_config.get_hint(key_config.merge_branch), - ), - "merge a branch", - CMD_GROUP_BRANCHES, - ) - } - pub fn select_branch_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Checkout [{}]", - key_config.get_hint(key_config.enter), - ), - "checkout branch", - CMD_GROUP_BRANCHES, - ) - } - pub fn toggle_branch_popup( - key_config: &SharedKeyConfig, - local: bool, - ) -> CommandText { - CommandText::new( - format!( - "{} [{}]", - if local { "Remote" } else { "Local" }, - key_config.get_hint(key_config.tab_toggle), - ), - "toggle branch type (remote/local)", - CMD_GROUP_BRANCHES, - ) - } - pub fn open_branch_select_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Branches [{}]", - key_config.get_hint(key_config.select_branch), - ), - "open branch popup", - CMD_GROUP_BRANCHES, - ) - } + pub fn diff_focus_left( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Back [{}]", + key_config.get_hint(key_config.focus_left), + ), + "view and select changed files", + CMD_GROUP_GENERAL, + ) + } + pub fn diff_focus_right( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Diff [{}]", + key_config.get_hint(key_config.focus_right), + ), + "inspect file diff", + CMD_GROUP_GENERAL, + ) + } + pub fn quit(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Quit [{}]", + key_config.get_hint(key_config.exit), + ), + "quit gitui application", + CMD_GROUP_GENERAL, + ) + } + pub fn confirm_action( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Confirm [{}]", + key_config.get_hint(key_config.enter), + ), + "confirm action", + CMD_GROUP_GENERAL, + ) + } + pub fn stashing_save( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Save [{}]", + key_config.get_hint(key_config.stashing_save), + ), + "opens stash name input popup", + CMD_GROUP_STASHING, + ) + } + pub fn stashing_toggle_indexed( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Toggle Staged [{}]", + key_config.get_hint(key_config.stashing_toggle_index), + ), + "toggle including staged files into stash", + CMD_GROUP_STASHING, + ) + } + pub fn stashing_toggle_untracked( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Toggle Untracked [{}]", + key_config + .get_hint(key_config.stashing_toggle_untracked), + ), + "toggle including untracked files into stash", + CMD_GROUP_STASHING, + ) + } + pub fn stashing_confirm_msg( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Stash [{}]", + key_config.get_hint(key_config.enter), + ), + "save files to stash", + CMD_GROUP_STASHING, + ) + } + pub fn stashlist_apply( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Apply [{}]", + key_config.get_hint(key_config.stash_apply), + ), + "apply selected stash", + CMD_GROUP_STASHES, + ) + } + pub fn stashlist_drop( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Drop [{}]", + key_config.get_hint(key_config.stash_drop), + ), + "drop selected stash", + CMD_GROUP_STASHES, + ) + } + pub fn stashlist_pop( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Pop [{}]", + key_config.get_hint(key_config.enter), + ), + "pop selected stash", + CMD_GROUP_STASHES, + ) + } + pub fn stashlist_inspect( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Inspect [{}]", + key_config.get_hint(key_config.focus_right), + ), + "open stash commit details (allows to diff files)", + CMD_GROUP_STASHES, + ) + } + pub fn log_details_toggle( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Details [{}]", + key_config.get_hint(key_config.enter), + ), + "open details of selected commit", + CMD_GROUP_LOG, + ) + } + pub fn log_details_open( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Inspect [{}]", + key_config.get_hint(key_config.focus_right), + ), + "inspect selected commit in detail", + CMD_GROUP_LOG, + ) + } + pub fn blame_file(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Blame [{}]", + key_config.get_hint(key_config.blame), + ), + "open blame view of selected file", + CMD_GROUP_LOG, + ) + } + pub fn log_tag_commit( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Tag [{}]", + key_config.get_hint(key_config.log_tag_commit), + ), + "tag commit", + CMD_GROUP_LOG, + ) + } + pub fn inspect_file_tree( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Files [{}]", + key_config.get_hint(key_config.open_file_tree), + ), + "inspect file tree at specific revision", + CMD_GROUP_LOG, + ) + } + pub fn tag_commit_confirm_msg( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Tag [{}]", + key_config.get_hint(key_config.enter), + ), + "tag commit", + CMD_GROUP_LOG, + ) + } + pub fn create_branch_confirm_msg( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Create Branch [{}]", + key_config.get_hint(key_config.enter), + ), + "create branch", + CMD_GROUP_BRANCHES, + ) + .hide_help() + } + pub fn open_branch_create_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Create [{}]", + key_config.get_hint(key_config.create_branch), + ), + "open create branch popup", + CMD_GROUP_BRANCHES, + ) + } + pub fn rename_branch_confirm_msg( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Rename Branch [{}]", + key_config.get_hint(key_config.enter), + ), + "rename branch", + CMD_GROUP_BRANCHES, + ) + .hide_help() + } + pub fn rename_branch_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Rename Branch [{}]", + key_config.get_hint(key_config.rename_branch), + ), + "rename branch", + CMD_GROUP_BRANCHES, + ) + } + pub fn delete_branch_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Delete [{}]", + key_config.get_hint(key_config.delete_branch), + ), + "delete a branch", + CMD_GROUP_BRANCHES, + ) + } + pub fn merge_branch_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Merge [{}]", + key_config.get_hint(key_config.merge_branch), + ), + "merge a branch", + CMD_GROUP_BRANCHES, + ) + } + pub fn select_branch_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Checkout [{}]", + key_config.get_hint(key_config.enter), + ), + "checkout branch", + CMD_GROUP_BRANCHES, + ) + } + pub fn toggle_branch_popup( + key_config: &SharedKeyConfig, + local: bool, + ) -> CommandText { + CommandText::new( + format!( + "{} [{}]", + if local { "Remote" } else { "Local" }, + key_config.get_hint(key_config.tab_toggle), + ), + "toggle branch type (remote/local)", + CMD_GROUP_BRANCHES, + ) + } + pub fn open_branch_select_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Branches [{}]", + key_config.get_hint(key_config.select_branch), + ), + "open branch popup", + CMD_GROUP_BRANCHES, + ) + } - pub fn open_tags_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Tags [{}]", - key_config.get_hint(key_config.tags), - ), - "open tags popup", - CMD_GROUP_GENERAL, - ) - } - pub fn delete_tag_popup( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Delete [{}]", - key_config.get_hint(key_config.delete_tag), - ), - "delete a tag", - CMD_GROUP_GENERAL, - ) - } - pub fn select_tag(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Select commit [{}]", - key_config.get_hint(key_config.select_tag), - ), - "Select commit in revlog", - CMD_GROUP_LOG, - ) - } + pub fn open_tags_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Tags [{}]", + key_config.get_hint(key_config.tags), + ), + "open tags popup", + CMD_GROUP_GENERAL, + ) + } + pub fn delete_tag_popup( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Delete [{}]", + key_config.get_hint(key_config.delete_tag), + ), + "delete a tag", + CMD_GROUP_GENERAL, + ) + } + pub fn select_tag(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Select commit [{}]", + key_config.get_hint(key_config.select_tag), + ), + "Select commit in revlog", + CMD_GROUP_LOG, + ) + } - pub fn status_push(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Push [{}]", - key_config.get_hint(key_config.push), - ), - "push to origin", - CMD_GROUP_GENERAL, - ) - } - pub fn status_force_push( - key_config: &SharedKeyConfig, - ) -> CommandText { - CommandText::new( - format!( - "Force Push [{}]", - key_config.get_hint(key_config.force_push), - ), - "force push to origin", - CMD_GROUP_GENERAL, - ) - } - pub fn status_pull(key_config: &SharedKeyConfig) -> CommandText { - CommandText::new( - format!( - "Pull [{}]", - key_config.get_hint(key_config.pull), - ), - "fetch/merge", - CMD_GROUP_GENERAL, - ) - } + pub fn status_push(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Push [{}]", + key_config.get_hint(key_config.push), + ), + "push to origin", + CMD_GROUP_GENERAL, + ) + } + pub fn status_force_push( + key_config: &SharedKeyConfig, + ) -> CommandText { + CommandText::new( + format!( + "Force Push [{}]", + key_config.get_hint(key_config.force_push), + ), + "force push to origin", + CMD_GROUP_GENERAL, + ) + } + pub fn status_pull(key_config: &SharedKeyConfig) -> CommandText { + CommandText::new( + format!( + "Pull [{}]", + key_config.get_hint(key_config.pull), + ), + "fetch/merge", + CMD_GROUP_GENERAL, + ) + } } diff --git a/src/tabs/files.rs b/src/tabs/files.rs index f500edd1..e5572dc6 100644 --- a/src/tabs/files.rs +++ b/src/tabs/files.rs @@ -1,121 +1,121 @@ #![allow( - dead_code, - clippy::missing_const_for_fn, - clippy::unused_self + dead_code, + clippy::missing_const_for_fn, + clippy::unused_self )] use crate::{ - components::{ - visibility_blocking, CommandBlocking, CommandInfo, Component, - DrawableComponent, EventState, RevisionFilesComponent, - }, - keys::SharedKeyConfig, - queue::Queue, - ui::style::SharedTheme, - AsyncAppNotification, AsyncNotification, + components::{ + visibility_blocking, CommandBlocking, CommandInfo, Component, + DrawableComponent, EventState, RevisionFilesComponent, + }, + keys::SharedKeyConfig, + queue::Queue, + ui::style::SharedTheme, + AsyncAppNotification, AsyncNotification, }; use anyhow::Result; use asyncgit::{sync, CWD}; use crossbeam_channel::Sender; pub struct FilesTab { - visible: bool, - theme: SharedTheme, - key_config: SharedKeyConfig, - files: RevisionFilesComponent, + visible: bool, + theme: SharedTheme, + key_config: SharedKeyConfig, + files: RevisionFilesComponent, } impl FilesTab { - /// - pub fn new( - sender: &Sender, - queue: &Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - visible: false, - files: RevisionFilesComponent::new( - queue, - sender, - theme.clone(), - key_config.clone(), - ), - theme, - key_config, - } - } + /// + pub fn new( + sender: &Sender, + queue: &Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + visible: false, + files: RevisionFilesComponent::new( + queue, + sender, + theme.clone(), + key_config.clone(), + ), + theme, + key_config, + } + } - /// - pub fn update(&mut self) -> Result<()> { - if self.is_visible() { - self.files.set_commit(sync::get_head(CWD)?)?; - } + /// + pub fn update(&mut self) -> Result<()> { + if self.is_visible() { + self.files.set_commit(sync::get_head(CWD)?)?; + } - Ok(()) - } + Ok(()) + } - /// - pub fn anything_pending(&self) -> bool { - self.files.any_work_pending() - } + /// + pub fn anything_pending(&self) -> bool { + self.files.any_work_pending() + } - /// - pub fn update_async(&mut self, ev: AsyncNotification) { - if self.is_visible() { - self.files.update(ev); - } - } + /// + pub fn update_async(&mut self, ev: AsyncNotification) { + if self.is_visible() { + self.files.update(ev); + } + } } impl DrawableComponent for FilesTab { - fn draw( - &self, - f: &mut tui::Frame, - rect: tui::layout::Rect, - ) -> Result<()> { - if self.is_visible() { - self.files.draw(f, rect)?; - } - Ok(()) - } + fn draw( + &self, + f: &mut tui::Frame, + rect: tui::layout::Rect, + ) -> Result<()> { + if self.is_visible() { + self.files.draw(f, rect)?; + } + Ok(()) + } } impl Component for FilesTab { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - return self.files.commands(out, force_all); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + return self.files.commands(out, force_all); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event( - &mut self, - ev: crossterm::event::Event, - ) -> Result { - if self.visible { - return self.files.event(ev); - } + fn event( + &mut self, + ev: crossterm::event::Event, + ) -> Result { + if self.visible { + return self.files.event(ev); + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; - self.update()?; - Ok(()) - } + fn show(&mut self) -> Result<()> { + self.visible = true; + self.update()?; + Ok(()) + } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 7a52a4a7..22c5b07d 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -1,358 +1,358 @@ use crate::{ - components::{ - visibility_blocking, CommandBlocking, CommandInfo, - CommitDetailsComponent, CommitList, Component, - DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings, - ui::style::SharedTheme, + components::{ + visibility_blocking, CommandBlocking, CommandInfo, + CommitDetailsComponent, CommitList, Component, + DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - cached, - sync::{self, CommitId}, - AsyncGitNotification, AsyncLog, AsyncTags, FetchStatus, CWD, + cached, + sync::{self, CommitId}, + AsyncGitNotification, AsyncLog, AsyncTags, FetchStatus, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use std::time::Duration; use sync::CommitTags; use tui::{ - backend::Backend, - layout::{Constraint, Direction, Layout, Rect}, - Frame, + backend::Backend, + layout::{Constraint, Direction, Layout, Rect}, + Frame, }; const SLICE_SIZE: usize = 1200; /// pub struct Revlog { - commit_details: CommitDetailsComponent, - list: CommitList, - git_log: AsyncLog, - git_tags: AsyncTags, - queue: Queue, - visible: bool, - branch_name: cached::BranchName, - key_config: SharedKeyConfig, + commit_details: CommitDetailsComponent, + list: CommitList, + git_log: AsyncLog, + git_tags: AsyncTags, + queue: Queue, + visible: bool, + branch_name: cached::BranchName, + key_config: SharedKeyConfig, } impl Revlog { - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - commit_details: CommitDetailsComponent::new( - queue, - sender, - theme.clone(), - key_config.clone(), - ), - list: CommitList::new( - &strings::log_title(&key_config), - theme, - key_config.clone(), - ), - git_log: AsyncLog::new(sender, None), - git_tags: AsyncTags::new(sender), - visible: false, - branch_name: cached::BranchName::new(CWD), - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + commit_details: CommitDetailsComponent::new( + queue, + sender, + theme.clone(), + key_config.clone(), + ), + list: CommitList::new( + &strings::log_title(&key_config), + theme, + key_config.clone(), + ), + git_log: AsyncLog::new(sender, None), + git_tags: AsyncTags::new(sender), + visible: false, + branch_name: cached::BranchName::new(CWD), + key_config, + } + } - /// - pub fn any_work_pending(&self) -> bool { - self.git_log.is_pending() - || self.git_tags.is_pending() - || self.commit_details.any_work_pending() - } + /// + pub fn any_work_pending(&self) -> bool { + self.git_log.is_pending() + || self.git_tags.is_pending() + || self.commit_details.any_work_pending() + } - /// - pub fn update(&mut self) -> Result<()> { - if self.is_visible() { - let log_changed = - self.git_log.fetch()? == FetchStatus::Started; + /// + pub fn update(&mut self) -> Result<()> { + if self.is_visible() { + let log_changed = + self.git_log.fetch()? == FetchStatus::Started; - self.list.set_count_total(self.git_log.count()?); + self.list.set_count_total(self.git_log.count()?); - let selection = self.list.selection(); - let selection_max = self.list.selection_max(); - if self.list.items().needs_data(selection, selection_max) - || log_changed - { - self.fetch_commits()?; - } + let selection = self.list.selection(); + let selection_max = self.list.selection_max(); + if self.list.items().needs_data(selection, selection_max) + || log_changed + { + self.fetch_commits()?; + } - self.git_tags.request(Duration::from_secs(3), false)?; + self.git_tags.request(Duration::from_secs(3), false)?; - self.list.set_branch( - self.branch_name.lookup().map(Some).unwrap_or(None), - ); + self.list.set_branch( + self.branch_name.lookup().map(Some).unwrap_or(None), + ); - if self.commit_details.is_visible() { - let commit = self.selected_commit(); - let tags = self.selected_commit_tags(&commit); + if self.commit_details.is_visible() { + let commit = self.selected_commit(); + let tags = self.selected_commit_tags(&commit); - self.commit_details.set_commit(commit, tags)?; - } - } + self.commit_details.set_commit(commit, tags)?; + } + } - Ok(()) - } + Ok(()) + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.visible { - match ev { - AsyncGitNotification::CommitFiles - | AsyncGitNotification::Log => self.update()?, - AsyncGitNotification::Tags => { - if let Some(tags) = self.git_tags.last()? { - self.list.set_tags(tags); - self.update()?; - } - } - _ => (), - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.visible { + match ev { + AsyncGitNotification::CommitFiles + | AsyncGitNotification::Log => self.update()?, + AsyncGitNotification::Tags => { + if let Some(tags) = self.git_tags.last()? { + self.list.set_tags(tags); + self.update()?; + } + } + _ => (), + } + } - Ok(()) - } + Ok(()) + } - fn fetch_commits(&mut self) -> Result<()> { - let want_min = - self.list.selection().saturating_sub(SLICE_SIZE / 2); + fn fetch_commits(&mut self) -> Result<()> { + let want_min = + self.list.selection().saturating_sub(SLICE_SIZE / 2); - let commits = sync::get_commits_info( - CWD, - &self.git_log.get_slice(want_min, SLICE_SIZE)?, - self.list.current_size().0.into(), - ); + let commits = sync::get_commits_info( + CWD, + &self.git_log.get_slice(want_min, SLICE_SIZE)?, + self.list.current_size().0.into(), + ); - if let Ok(commits) = commits { - self.list.items().set_items(want_min, commits); - } + if let Ok(commits) = commits { + self.list.items().set_items(want_min, commits); + } - Ok(()) - } + Ok(()) + } - fn selected_commit(&self) -> Option { - self.list.selected_entry().map(|e| e.id) - } + fn selected_commit(&self) -> Option { + self.list.selected_entry().map(|e| e.id) + } - fn copy_commit_hash(&self) -> Result<()> { - self.list.copy_entry_hash()?; - Ok(()) - } + fn copy_commit_hash(&self) -> Result<()> { + self.list.copy_entry_hash()?; + Ok(()) + } - fn selected_commit_tags( - &self, - commit: &Option, - ) -> Option { - let tags = self.list.tags(); + fn selected_commit_tags( + &self, + commit: &Option, + ) -> Option { + let tags = self.list.tags(); - commit.and_then(|commit| { - tags.and_then(|tags| tags.get(&commit).cloned()) - }) - } + commit.and_then(|commit| { + tags.and_then(|tags| tags.get(&commit).cloned()) + }) + } - pub fn select_commit(&mut self, id: CommitId) -> Result<()> { - let position = self.git_log.position(id)?; + pub fn select_commit(&mut self, id: CommitId) -> Result<()> { + let position = self.git_log.position(id)?; - if let Some(position) = position { - self.list.select_entry(position); + if let Some(position) = position { + self.list.select_entry(position); - Ok(()) - } else { - anyhow::bail!("Could not select commit in revlog. It might not be loaded yet or it might be on a different branch."); - } - } + Ok(()) + } else { + anyhow::bail!("Could not select commit in revlog. It might not be loaded yet or it might be on a different branch."); + } + } } impl DrawableComponent for Revlog { - fn draw( - &self, - f: &mut Frame, - area: Rect, - ) -> Result<()> { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage(60), - Constraint::Percentage(40), - ] - .as_ref(), - ) - .split(area); + fn draw( + &self, + f: &mut Frame, + area: Rect, + ) -> Result<()> { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(60), + Constraint::Percentage(40), + ] + .as_ref(), + ) + .split(area); - if self.commit_details.is_visible() { - self.list.draw(f, chunks[0])?; - self.commit_details.draw(f, chunks[1])?; - } else { - self.list.draw(f, area)?; - } + if self.commit_details.is_visible() { + self.list.draw(f, chunks[0])?; + self.commit_details.draw(f, chunks[1])?; + } else { + self.list.draw(f, area)?; + } - Ok(()) - } + Ok(()) + } } impl Component for Revlog { - fn event(&mut self, ev: Event) -> Result { - if self.visible { - let event_used = self.list.event(ev)?; + fn event(&mut self, ev: Event) -> Result { + if self.visible { + let event_used = self.list.event(ev)?; - if event_used.is_consumed() { - self.update()?; - return Ok(EventState::Consumed); - } else if let Event::Key(k) = ev { - if k == self.key_config.enter { - self.commit_details.toggle_visible()?; - self.update()?; - return Ok(EventState::Consumed); - } else if k == self.key_config.copy { - self.copy_commit_hash()?; - return Ok(EventState::Consumed); - } else if k == self.key_config.push { - self.queue.push(InternalEvent::PushTags); - return Ok(EventState::Consumed); - } else if k == self.key_config.log_tag_commit { - return self.selected_commit().map_or( - Ok(EventState::NotConsumed), - |id| { - self.queue - .push(InternalEvent::TagCommit(id)); - Ok(EventState::Consumed) - }, - ); - } else if k == self.key_config.focus_right - && self.commit_details.is_visible() - { - return self.selected_commit().map_or( - Ok(EventState::NotConsumed), - |id| { - self.queue.push( - InternalEvent::InspectCommit( - id, - self.selected_commit_tags(&Some( - id, - )), - ), - ); - Ok(EventState::Consumed) - }, - ); - } else if k == self.key_config.select_branch { - self.queue.push(InternalEvent::SelectBranch); - return Ok(EventState::Consumed); - } else if k == self.key_config.open_file_tree { - return self.selected_commit().map_or( - Ok(EventState::NotConsumed), - |id| { - self.queue.push( - InternalEvent::OpenFileTree(id), - ); - Ok(EventState::Consumed) - }, - ); - } else if k == self.key_config.tags { - self.queue.push(InternalEvent::Tags); - return Ok(EventState::Consumed); - } - } - } + if event_used.is_consumed() { + self.update()?; + return Ok(EventState::Consumed); + } else if let Event::Key(k) = ev { + if k == self.key_config.enter { + self.commit_details.toggle_visible()?; + self.update()?; + return Ok(EventState::Consumed); + } else if k == self.key_config.copy { + self.copy_commit_hash()?; + return Ok(EventState::Consumed); + } else if k == self.key_config.push { + self.queue.push(InternalEvent::PushTags); + return Ok(EventState::Consumed); + } else if k == self.key_config.log_tag_commit { + return self.selected_commit().map_or( + Ok(EventState::NotConsumed), + |id| { + self.queue + .push(InternalEvent::TagCommit(id)); + Ok(EventState::Consumed) + }, + ); + } else if k == self.key_config.focus_right + && self.commit_details.is_visible() + { + return self.selected_commit().map_or( + Ok(EventState::NotConsumed), + |id| { + self.queue.push( + InternalEvent::InspectCommit( + id, + self.selected_commit_tags(&Some( + id, + )), + ), + ); + Ok(EventState::Consumed) + }, + ); + } else if k == self.key_config.select_branch { + self.queue.push(InternalEvent::SelectBranch); + return Ok(EventState::Consumed); + } else if k == self.key_config.open_file_tree { + return self.selected_commit().map_or( + Ok(EventState::NotConsumed), + |id| { + self.queue.push( + InternalEvent::OpenFileTree(id), + ); + Ok(EventState::Consumed) + }, + ); + } else if k == self.key_config.tags { + self.queue.push(InternalEvent::Tags); + return Ok(EventState::Consumed); + } + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - self.list.commands(out, force_all); - } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + self.list.commands(out, force_all); + } - out.push(CommandInfo::new( - strings::commands::log_details_toggle(&self.key_config), - true, - self.visible, - )); + out.push(CommandInfo::new( + strings::commands::log_details_toggle(&self.key_config), + true, + self.visible, + )); - out.push(CommandInfo::new( - strings::commands::log_details_open(&self.key_config), - true, - (self.visible && self.commit_details.is_visible()) - || force_all, - )); + out.push(CommandInfo::new( + strings::commands::log_details_open(&self.key_config), + true, + (self.visible && self.commit_details.is_visible()) + || force_all, + )); - out.push(CommandInfo::new( - strings::commands::log_tag_commit(&self.key_config), - self.selected_commit().is_some(), - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::log_tag_commit(&self.key_config), + self.selected_commit().is_some(), + self.visible || force_all, + )); - out.push(CommandInfo::new( - strings::commands::open_branch_select_popup( - &self.key_config, - ), - true, - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::open_branch_select_popup( + &self.key_config, + ), + true, + self.visible || force_all, + )); - out.push(CommandInfo::new( - strings::commands::open_tags_popup(&self.key_config), - true, - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::open_tags_popup(&self.key_config), + true, + self.visible || force_all, + )); - out.push(CommandInfo::new( - strings::commands::copy_hash(&self.key_config), - self.selected_commit().is_some(), - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::copy_hash(&self.key_config), + self.selected_commit().is_some(), + self.visible || force_all, + )); - out.push(CommandInfo::new( - strings::commands::push_tags(&self.key_config), - true, - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::push_tags(&self.key_config), + true, + self.visible || force_all, + )); - out.push(CommandInfo::new( - strings::commands::inspect_file_tree(&self.key_config), - self.selected_commit().is_some(), - self.visible || force_all, - )); + out.push(CommandInfo::new( + strings::commands::inspect_file_tree(&self.key_config), + self.selected_commit().is_some(), + self.visible || force_all, + )); - visibility_blocking(self) - } + visibility_blocking(self) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - self.git_log.set_background(); - } + fn hide(&mut self) { + self.visible = false; + self.git_log.set_background(); + } - fn show(&mut self) -> Result<()> { - self.visible = true; - self.list.clear(); - self.update()?; + fn show(&mut self) -> Result<()> { + self.visible = true; + self.list.clear(); + self.update()?; - Ok(()) - } + Ok(()) + } } diff --git a/src/tabs/stashing.rs b/src/tabs/stashing.rs index 9cd1bde0..bbbc513d 100644 --- a/src/tabs/stashing.rs +++ b/src/tabs/stashing.rs @@ -1,269 +1,269 @@ use crate::{ - accessors, - components::{ - command_pump, event_pump, visibility_blocking, - CommandBlocking, CommandInfo, Component, DrawableComponent, - EventState, FileTreeComponent, - }, - keys::SharedKeyConfig, - queue::{InternalEvent, Queue}, - strings, - ui::style::SharedTheme, + accessors, + components::{ + command_pump, event_pump, visibility_blocking, + CommandBlocking, CommandInfo, Component, DrawableComponent, + EventState, FileTreeComponent, + }, + keys::SharedKeyConfig, + queue::{InternalEvent, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{self, status::StatusType}, - AsyncGitNotification, AsyncStatus, StatusParams, CWD, + sync::{self, status::StatusType}, + AsyncGitNotification, AsyncStatus, StatusParams, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; use std::borrow::Cow; use tui::{ - layout::{Alignment, Constraint, Direction, Layout}, - text::{Span, Spans}, - widgets::{Block, Borders, Paragraph}, + layout::{Alignment, Constraint, Direction, Layout}, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, }; #[derive(Default, Clone, Copy, Debug)] pub struct StashingOptions { - pub stash_untracked: bool, - pub keep_index: bool, + pub stash_untracked: bool, + pub keep_index: bool, } pub struct Stashing { - index: FileTreeComponent, - visible: bool, - options: StashingOptions, - theme: SharedTheme, - git_status: AsyncStatus, - queue: Queue, - key_config: SharedKeyConfig, + index: FileTreeComponent, + visible: bool, + options: StashingOptions, + theme: SharedTheme, + git_status: AsyncStatus, + queue: Queue, + key_config: SharedKeyConfig, } impl Stashing { - accessors!(self, [index]); + accessors!(self, [index]); - /// - pub fn new( - sender: &Sender, - queue: &Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - index: FileTreeComponent::new( - &strings::stashing_files_title(&key_config), - true, - Some(queue.clone()), - theme.clone(), - key_config.clone(), - ), - visible: false, - options: StashingOptions { - keep_index: false, - stash_untracked: true, - }, - theme, - git_status: AsyncStatus::new(sender.clone()), - queue: queue.clone(), - key_config, - } - } + /// + pub fn new( + sender: &Sender, + queue: &Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + index: FileTreeComponent::new( + &strings::stashing_files_title(&key_config), + true, + Some(queue.clone()), + theme.clone(), + key_config.clone(), + ), + visible: false, + options: StashingOptions { + keep_index: false, + stash_untracked: true, + }, + theme, + git_status: AsyncStatus::new(sender.clone()), + queue: queue.clone(), + key_config, + } + } - /// - pub fn update(&mut self) -> Result<()> { - if self.is_visible() { - self.git_status - .fetch(&StatusParams::new(StatusType::Both))?; - } + /// + pub fn update(&mut self) -> Result<()> { + if self.is_visible() { + self.git_status + .fetch(&StatusParams::new(StatusType::Both))?; + } - Ok(()) - } + Ok(()) + } - /// - pub fn anything_pending(&self) -> bool { - self.git_status.is_pending() - } + /// + pub fn anything_pending(&self) -> bool { + self.git_status.is_pending() + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - if self.is_visible() { - if let AsyncGitNotification::Status = ev { - let status = self.git_status.last()?; - self.index.update(&status.items)?; - } - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + if self.is_visible() { + if let AsyncGitNotification::Status = ev { + let status = self.git_status.last()?; + self.index.update(&status.items)?; + } + } - Ok(()) - } + Ok(()) + } - fn get_option_text(&self) -> Vec { - let bracket_open = Span::raw(Cow::from("[")); - let bracket_close = Span::raw(Cow::from("]")); - let option_on = - Span::styled(Cow::from("x"), self.theme.option(true)); + fn get_option_text(&self) -> Vec { + let bracket_open = Span::raw(Cow::from("[")); + let bracket_close = Span::raw(Cow::from("]")); + let option_on = + Span::styled(Cow::from("x"), self.theme.option(true)); - let option_off = - Span::styled(Cow::from("_"), self.theme.option(false)); + let option_off = + Span::styled(Cow::from("_"), self.theme.option(false)); - vec![ - Spans::from(vec![ - bracket_open.clone(), - if self.options.stash_untracked { - option_on.clone() - } else { - option_off.clone() - }, - bracket_close.clone(), - Span::raw(Cow::from(" stash untracked")), - ]), - Spans::from(vec![ - bracket_open, - if self.options.keep_index { - option_on.clone() - } else { - option_off.clone() - }, - bracket_close, - Span::raw(Cow::from(" keep index")), - ]), - ] - } + vec![ + Spans::from(vec![ + bracket_open.clone(), + if self.options.stash_untracked { + option_on.clone() + } else { + option_off.clone() + }, + bracket_close.clone(), + Span::raw(Cow::from(" stash untracked")), + ]), + Spans::from(vec![ + bracket_open, + if self.options.keep_index { + option_on.clone() + } else { + option_off.clone() + }, + bracket_close, + Span::raw(Cow::from(" keep index")), + ]), + ] + } } impl DrawableComponent for Stashing { - fn draw( - &self, - f: &mut tui::Frame, - rect: tui::layout::Rect, - ) -> Result<()> { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [Constraint::Min(1), Constraint::Length(22)].as_ref(), - ) - .split(rect); + fn draw( + &self, + f: &mut tui::Frame, + rect: tui::layout::Rect, + ) -> Result<()> { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [Constraint::Min(1), Constraint::Length(22)].as_ref(), + ) + .split(rect); - let right_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [Constraint::Length(4), Constraint::Min(1)].as_ref(), - ) - .split(chunks[1]); + let right_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [Constraint::Length(4), Constraint::Min(1)].as_ref(), + ) + .split(chunks[1]); - f.render_widget( - Paragraph::new(self.get_option_text()) - .block(Block::default().borders(Borders::ALL).title( - strings::stashing_options_title(&self.key_config), - )) - .alignment(Alignment::Left), - right_chunks[0], - ); + f.render_widget( + Paragraph::new(self.get_option_text()) + .block(Block::default().borders(Borders::ALL).title( + strings::stashing_options_title(&self.key_config), + )) + .alignment(Alignment::Left), + right_chunks[0], + ); - self.index.draw(f, chunks[0])?; + self.index.draw(f, chunks[0])?; - Ok(()) - } + Ok(()) + } } impl Component for Stashing { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - command_pump( - out, - force_all, - self.components().as_slice(), - ); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + command_pump( + out, + force_all, + self.components().as_slice(), + ); - out.push(CommandInfo::new( - strings::commands::stashing_save(&self.key_config), - self.visible && !self.index.is_empty(), - self.visible || force_all, - )); - out.push(CommandInfo::new( - strings::commands::stashing_toggle_indexed( - &self.key_config, - ), - self.visible, - self.visible || force_all, - )); - out.push(CommandInfo::new( - strings::commands::stashing_toggle_untracked( - &self.key_config, - ), - self.visible, - self.visible || force_all, - )); - } + out.push(CommandInfo::new( + strings::commands::stashing_save(&self.key_config), + self.visible && !self.index.is_empty(), + self.visible || force_all, + )); + out.push(CommandInfo::new( + strings::commands::stashing_toggle_indexed( + &self.key_config, + ), + self.visible, + self.visible || force_all, + )); + out.push(CommandInfo::new( + strings::commands::stashing_toggle_untracked( + &self.key_config, + ), + self.visible, + self.visible || force_all, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event( - &mut self, - ev: crossterm::event::Event, - ) -> Result { - if self.visible { - if event_pump(ev, self.components_mut().as_mut_slice())? - .is_consumed() - { - return Ok(EventState::Consumed); - } + fn event( + &mut self, + ev: crossterm::event::Event, + ) -> Result { + if self.visible { + if event_pump(ev, self.components_mut().as_mut_slice())? + .is_consumed() + { + return Ok(EventState::Consumed); + } - if let Event::Key(k) = ev { - return if k == self.key_config.stashing_save - && !self.index.is_empty() - { - self.queue.push(InternalEvent::PopupStashing( - self.options, - )); + if let Event::Key(k) = ev { + return if k == self.key_config.stashing_save + && !self.index.is_empty() + { + self.queue.push(InternalEvent::PopupStashing( + self.options, + )); - Ok(EventState::Consumed) - } else if k == self.key_config.stashing_toggle_index { - self.options.keep_index = - !self.options.keep_index; - self.update()?; - Ok(EventState::Consumed) - } else if k - == self.key_config.stashing_toggle_untracked - { - self.options.stash_untracked = - !self.options.stash_untracked; - self.update()?; - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - }; - }; - } + Ok(EventState::Consumed) + } else if k == self.key_config.stashing_toggle_index { + self.options.keep_index = + !self.options.keep_index; + self.update()?; + Ok(EventState::Consumed) + } else if k + == self.key_config.stashing_toggle_untracked + { + self.options.stash_untracked = + !self.options.stash_untracked; + self.update()?; + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + }; + }; + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - let config_untracked_files = - sync::untracked_files_config(CWD)?; + fn show(&mut self) -> Result<()> { + let config_untracked_files = + sync::untracked_files_config(CWD)?; - self.options.stash_untracked = - !config_untracked_files.include_none(); + self.options.stash_untracked = + !config_untracked_files.include_none(); - self.visible = true; - self.update()?; - Ok(()) - } + self.visible = true; + self.update()?; + Ok(()) + } } diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index a089e667..f7c50309 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -1,212 +1,212 @@ use crate::{ - components::{ - visibility_blocking, CommandBlocking, CommandInfo, - CommitList, Component, DrawableComponent, EventState, - }, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, Queue}, - strings, - ui::style::SharedTheme, + components::{ + visibility_blocking, CommandBlocking, CommandInfo, + CommitList, Component, DrawableComponent, EventState, + }, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, Queue}, + strings, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - sync::{self, CommitId}, - CWD, + sync::{self, CommitId}, + CWD, }; use crossterm::event::Event; pub struct StashList { - list: CommitList, - visible: bool, - queue: Queue, - key_config: SharedKeyConfig, + list: CommitList, + visible: bool, + queue: Queue, + key_config: SharedKeyConfig, } impl StashList { - /// - pub fn new( - queue: &Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - visible: false, - list: CommitList::new( - &strings::stashlist_title(&key_config), - theme, - key_config.clone(), - ), - queue: queue.clone(), - key_config, - } - } + /// + pub fn new( + queue: &Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + visible: false, + list: CommitList::new( + &strings::stashlist_title(&key_config), + theme, + key_config.clone(), + ), + queue: queue.clone(), + key_config, + } + } - /// - pub fn update(&mut self) -> Result<()> { - if self.is_visible() { - let stashes = sync::get_stashes(CWD)?; - let commits = - sync::get_commits_info(CWD, stashes.as_slice(), 100)?; + /// + pub fn update(&mut self) -> Result<()> { + if self.is_visible() { + let stashes = sync::get_stashes(CWD)?; + let commits = + sync::get_commits_info(CWD, stashes.as_slice(), 100)?; - self.list.set_count_total(commits.len()); - self.list.items().set_items(0, commits); - } + self.list.set_count_total(commits.len()); + self.list.items().set_items(0, commits); + } - Ok(()) - } + Ok(()) + } - fn apply_stash(&mut self) { - if let Some(e) = self.list.selected_entry() { - match sync::stash_apply(CWD, e.id, false) { - Ok(_) => { - self.queue.push(InternalEvent::TabSwitch); - } - Err(e) => { - self.queue.push(InternalEvent::ShowErrorMsg( - format!("stash apply error:\n{}", e,), - )); - } - } - } - } + fn apply_stash(&mut self) { + if let Some(e) = self.list.selected_entry() { + match sync::stash_apply(CWD, e.id, false) { + Ok(_) => { + self.queue.push(InternalEvent::TabSwitch); + } + Err(e) => { + self.queue.push(InternalEvent::ShowErrorMsg( + format!("stash apply error:\n{}", e,), + )); + } + } + } + } - fn drop_stash(&mut self) { - if let Some(e) = self.list.selected_entry() { - self.queue.push(InternalEvent::ConfirmAction( - Action::StashDrop(e.id), - )); - } - } + fn drop_stash(&mut self) { + if let Some(e) = self.list.selected_entry() { + self.queue.push(InternalEvent::ConfirmAction( + Action::StashDrop(e.id), + )); + } + } - fn pop_stash(&mut self) { - if let Some(e) = self.list.selected_entry() { - self.queue.push(InternalEvent::ConfirmAction( - Action::StashPop(e.id), - )); - } - } + fn pop_stash(&mut self) { + if let Some(e) = self.list.selected_entry() { + self.queue.push(InternalEvent::ConfirmAction( + Action::StashPop(e.id), + )); + } + } - fn inspect(&mut self) { - if let Some(e) = self.list.selected_entry() { - self.queue.push(InternalEvent::InspectCommit(e.id, None)); - } - } + fn inspect(&mut self) { + if let Some(e) = self.list.selected_entry() { + self.queue.push(InternalEvent::InspectCommit(e.id, None)); + } + } - /// Called when a pending stash action has been confirmed - pub fn action_confirmed(&self, action: &Action) -> bool { - match *action { - Action::StashDrop(id) => Self::drop(id), - Action::StashPop(id) => self.pop(id), - _ => false, - } - } + /// Called when a pending stash action has been confirmed + pub fn action_confirmed(&self, action: &Action) -> bool { + match *action { + Action::StashDrop(id) => Self::drop(id), + Action::StashPop(id) => self.pop(id), + _ => false, + } + } - fn drop(id: CommitId) -> bool { - sync::stash_drop(CWD, id).is_ok() - } + fn drop(id: CommitId) -> bool { + sync::stash_drop(CWD, id).is_ok() + } - fn pop(&self, id: CommitId) -> bool { - match sync::stash_pop(CWD, id) { - Ok(_) => { - self.queue.push(InternalEvent::TabSwitch); - true - } - Err(e) => { - self.queue.push(InternalEvent::ShowErrorMsg( - format!("stash pop error:\n{}", e,), - )); - true - } - } - } + fn pop(&self, id: CommitId) -> bool { + match sync::stash_pop(CWD, id) { + Ok(_) => { + self.queue.push(InternalEvent::TabSwitch); + true + } + Err(e) => { + self.queue.push(InternalEvent::ShowErrorMsg( + format!("stash pop error:\n{}", e,), + )); + true + } + } + } } impl DrawableComponent for StashList { - fn draw( - &self, - f: &mut tui::Frame, - rect: tui::layout::Rect, - ) -> Result<()> { - self.list.draw(f, rect)?; + fn draw( + &self, + f: &mut tui::Frame, + rect: tui::layout::Rect, + ) -> Result<()> { + self.list.draw(f, rect)?; - Ok(()) - } + Ok(()) + } } impl Component for StashList { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - if self.visible || force_all { - self.list.commands(out, force_all); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + if self.visible || force_all { + self.list.commands(out, force_all); - let selection_valid = - self.list.selected_entry().is_some(); - out.push(CommandInfo::new( - strings::commands::stashlist_pop(&self.key_config), - selection_valid, - true, - )); - out.push(CommandInfo::new( - strings::commands::stashlist_apply(&self.key_config), - selection_valid, - true, - )); - out.push(CommandInfo::new( - strings::commands::stashlist_drop(&self.key_config), - selection_valid, - true, - )); - out.push(CommandInfo::new( - strings::commands::stashlist_inspect( - &self.key_config, - ), - selection_valid, - true, - )); - } + let selection_valid = + self.list.selected_entry().is_some(); + out.push(CommandInfo::new( + strings::commands::stashlist_pop(&self.key_config), + selection_valid, + true, + )); + out.push(CommandInfo::new( + strings::commands::stashlist_apply(&self.key_config), + selection_valid, + true, + )); + out.push(CommandInfo::new( + strings::commands::stashlist_drop(&self.key_config), + selection_valid, + true, + )); + out.push(CommandInfo::new( + strings::commands::stashlist_inspect( + &self.key_config, + ), + selection_valid, + true, + )); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event( - &mut self, - ev: crossterm::event::Event, - ) -> Result { - if self.is_visible() { - if self.list.event(ev)?.is_consumed() { - return Ok(EventState::Consumed); - } + fn event( + &mut self, + ev: crossterm::event::Event, + ) -> Result { + if self.is_visible() { + if self.list.event(ev)?.is_consumed() { + return Ok(EventState::Consumed); + } - if let Event::Key(k) = ev { - if k == self.key_config.enter { - self.pop_stash(); - } else if k == self.key_config.stash_apply { - self.apply_stash(); - } else if k == self.key_config.stash_drop { - self.drop_stash(); - } else if k == self.key_config.stash_open { - self.inspect(); - } - } - } + if let Event::Key(k) = ev { + if k == self.key_config.enter { + self.pop_stash(); + } else if k == self.key_config.stash_apply { + self.apply_stash(); + } else if k == self.key_config.stash_drop { + self.drop_stash(); + } else if k == self.key_config.stash_open { + self.inspect(); + } + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; - self.update()?; - Ok(()) - } + fn show(&mut self) -> Result<()> { + self.visible = true; + self.update()?; + Ok(()) + } } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 8625e52f..d766fcd1 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -1,23 +1,23 @@ use crate::{ - accessors, - components::{ - command_pump, event_pump, visibility_blocking, - ChangesComponent, CommandBlocking, CommandInfo, Component, - DiffComponent, DrawableComponent, EventState, - FileTreeItemKind, - }, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, - strings, try_or_popup, - ui::style::SharedTheme, + accessors, + components::{ + command_pump, event_pump, visibility_blocking, + ChangesComponent, CommandBlocking, CommandInfo, Component, + DiffComponent, DrawableComponent, EventState, + FileTreeItemKind, + }, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, + strings, try_or_popup, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - cached, - sync::BranchCompare, - sync::{self, status::StatusType, RepoState}, - AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams, - DiffType, StatusParams, CWD, + cached, + sync::BranchCompare, + sync::{self, status::StatusType, RepoState}, + AsyncDiff, AsyncGitNotification, AsyncStatus, DiffParams, + DiffType, StatusParams, CWD, }; use crossbeam_channel::Sender; use crossterm::event::Event; @@ -25,713 +25,711 @@ use itertools::Itertools; use std::convert::Into; use std::convert::TryFrom; use tui::{ - layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Style}, - widgets::Paragraph, + layout::{Alignment, Constraint, Direction, Layout}, + style::{Color, Style}, + widgets::Paragraph, }; /// what part of the screen is focused #[derive(PartialEq)] enum Focus { - WorkDir, - Diff, - Stage, + WorkDir, + Diff, + Stage, } /// focus can toggle between workdir and stage impl Focus { - const fn toggled_focus(&self) -> Self { - match self { - Self::WorkDir => Self::Stage, - Self::Stage => Self::WorkDir, - Self::Diff => Self::Diff, - } - } + const fn toggled_focus(&self) -> Self { + match self { + Self::WorkDir => Self::Stage, + Self::Stage => Self::WorkDir, + Self::Diff => Self::Diff, + } + } } /// which target are we showing a diff against #[derive(PartialEq, Copy, Clone)] enum DiffTarget { - Stage, - WorkingDir, + Stage, + WorkingDir, } pub struct Status { - visible: bool, - focus: Focus, - diff_target: DiffTarget, - index: ChangesComponent, - index_wd: ChangesComponent, - diff: DiffComponent, - git_diff: AsyncDiff, - git_status_workdir: AsyncStatus, - git_status_stage: AsyncStatus, - git_branch_state: Option, - git_branch_name: cached::BranchName, - queue: Queue, - git_action_executed: bool, - key_config: SharedKeyConfig, + visible: bool, + focus: Focus, + diff_target: DiffTarget, + index: ChangesComponent, + index_wd: ChangesComponent, + diff: DiffComponent, + git_diff: AsyncDiff, + git_status_workdir: AsyncStatus, + git_status_stage: AsyncStatus, + git_branch_state: Option, + git_branch_name: cached::BranchName, + queue: Queue, + git_action_executed: bool, + key_config: SharedKeyConfig, } impl DrawableComponent for Status { - fn draw( - &self, - f: &mut tui::Frame, - rect: tui::layout::Rect, - ) -> Result<()> { - let chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - if self.focus == Focus::Diff { - [ - Constraint::Percentage(30), - Constraint::Percentage(70), - ] - } else { - [ - Constraint::Percentage(50), - Constraint::Percentage(50), - ] - } - .as_ref(), - ) - .split(rect); + fn draw( + &self, + f: &mut tui::Frame, + rect: tui::layout::Rect, + ) -> Result<()> { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + if self.focus == Focus::Diff { + [ + Constraint::Percentage(30), + Constraint::Percentage(70), + ] + } else { + [ + Constraint::Percentage(50), + Constraint::Percentage(50), + ] + } + .as_ref(), + ) + .split(rect); - let left_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - if self.diff_target == DiffTarget::WorkingDir { - [ - Constraint::Percentage(60), - Constraint::Percentage(40), - ] - } else { - [ - Constraint::Percentage(40), - Constraint::Percentage(60), - ] - } - .as_ref(), - ) - .split(chunks[0]); + let left_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + if self.diff_target == DiffTarget::WorkingDir { + [ + Constraint::Percentage(60), + Constraint::Percentage(40), + ] + } else { + [ + Constraint::Percentage(40), + Constraint::Percentage(60), + ] + } + .as_ref(), + ) + .split(chunks[0]); - self.index_wd.draw(f, left_chunks[0])?; - self.index.draw(f, left_chunks[1])?; - self.diff.draw(f, chunks[1])?; - self.draw_branch_state(f, &left_chunks); - Self::draw_repo_state(f, left_chunks[0])?; + self.index_wd.draw(f, left_chunks[0])?; + self.index.draw(f, left_chunks[1])?; + self.diff.draw(f, chunks[1])?; + self.draw_branch_state(f, &left_chunks); + Self::draw_repo_state(f, left_chunks[0])?; - Ok(()) - } + Ok(()) + } } impl Status { - accessors!(self, [index, index_wd, diff]); + accessors!(self, [index, index_wd, diff]); - /// - pub fn new( - queue: &Queue, - sender: &Sender, - theme: SharedTheme, - key_config: SharedKeyConfig, - ) -> Self { - Self { - queue: queue.clone(), - visible: true, - focus: Focus::WorkDir, - diff_target: DiffTarget::WorkingDir, - index_wd: ChangesComponent::new( - &strings::title_status(&key_config), - true, - true, - queue.clone(), - theme.clone(), - key_config.clone(), - ), - index: ChangesComponent::new( - &strings::title_index(&key_config), - false, - false, - queue.clone(), - theme.clone(), - key_config.clone(), - ), - diff: DiffComponent::new( - queue.clone(), - theme, - key_config.clone(), - false, - ), - git_diff: AsyncDiff::new(sender), - git_status_workdir: AsyncStatus::new(sender.clone()), - git_status_stage: AsyncStatus::new(sender.clone()), - git_action_executed: false, - git_branch_state: None, - git_branch_name: cached::BranchName::new(CWD), - key_config, - } - } + /// + pub fn new( + queue: &Queue, + sender: &Sender, + theme: SharedTheme, + key_config: SharedKeyConfig, + ) -> Self { + Self { + queue: queue.clone(), + visible: true, + focus: Focus::WorkDir, + diff_target: DiffTarget::WorkingDir, + index_wd: ChangesComponent::new( + &strings::title_status(&key_config), + true, + true, + queue.clone(), + theme.clone(), + key_config.clone(), + ), + index: ChangesComponent::new( + &strings::title_index(&key_config), + false, + false, + queue.clone(), + theme.clone(), + key_config.clone(), + ), + diff: DiffComponent::new( + queue.clone(), + theme, + key_config.clone(), + false, + ), + git_diff: AsyncDiff::new(sender), + git_status_workdir: AsyncStatus::new(sender.clone()), + git_status_stage: AsyncStatus::new(sender.clone()), + git_action_executed: false, + git_branch_state: None, + git_branch_name: cached::BranchName::new(CWD), + key_config, + } + } - fn draw_branch_state( - &self, - f: &mut tui::Frame, - chunks: &[tui::layout::Rect], - ) { - if let Some(branch_name) = self.git_branch_name.last() { - let ahead_behind = - if let Some(state) = &self.git_branch_state { - format!( - "\u{2191}{} \u{2193}{} ", - state.ahead, state.behind, - ) - } else { - String::new() - }; - let w = Paragraph::new(format!( - "{}{{{}}}", - ahead_behind, branch_name - )) - .alignment(Alignment::Right); + fn draw_branch_state( + &self, + f: &mut tui::Frame, + chunks: &[tui::layout::Rect], + ) { + if let Some(branch_name) = self.git_branch_name.last() { + let ahead_behind = + if let Some(state) = &self.git_branch_state { + format!( + "\u{2191}{} \u{2193}{} ", + state.ahead, state.behind, + ) + } else { + String::new() + }; + let w = Paragraph::new(format!( + "{}{{{}}}", + ahead_behind, branch_name + )) + .alignment(Alignment::Right); - let mut rect = if self.index_wd.focused() { - let mut rect = chunks[0]; - rect.y += rect.height.saturating_sub(1); - rect - } else { - chunks[1] - }; + let mut rect = if self.index_wd.focused() { + let mut rect = chunks[0]; + rect.y += rect.height.saturating_sub(1); + rect + } else { + chunks[1] + }; - rect.x += 1; - rect.width = rect.width.saturating_sub(2); - rect.height = rect - .height - .saturating_sub(rect.height.saturating_sub(1)); + rect.x += 1; + rect.width = rect.width.saturating_sub(2); + rect.height = rect + .height + .saturating_sub(rect.height.saturating_sub(1)); - f.render_widget(w, rect); - } - } + f.render_widget(w, rect); + } + } - fn draw_repo_state( - f: &mut tui::Frame, - r: tui::layout::Rect, - ) -> Result<()> { - if let Ok(state) = sync::repo_state(CWD) { - if state != RepoState::Clean { - let ids = - sync::mergehead_ids(CWD).unwrap_or_default(); - let ids = format!( - "({})", - ids.iter() - .map(|id| sync::CommitId::get_short_string( - id - )) - .join(",") - ); - let txt = format!("{:?} {}", state, ids); - let txt_len = u16::try_from(txt.len())?; - let w = Paragraph::new(txt) - .style(Style::default().fg(Color::Red)) - .alignment(Alignment::Left); + fn draw_repo_state( + f: &mut tui::Frame, + r: tui::layout::Rect, + ) -> Result<()> { + if let Ok(state) = sync::repo_state(CWD) { + if state != RepoState::Clean { + let ids = + sync::mergehead_ids(CWD).unwrap_or_default(); + let ids = format!( + "({})", + ids.iter() + .map(|id| sync::CommitId::get_short_string( + id + )) + .join(",") + ); + let txt = format!("{:?} {}", state, ids); + let txt_len = u16::try_from(txt.len())?; + let w = Paragraph::new(txt) + .style(Style::default().fg(Color::Red)) + .alignment(Alignment::Left); - let mut rect = r; - rect.x += 1; - rect.width = - rect.width.saturating_sub(2).min(txt_len); - rect.y += rect.height.saturating_sub(1); - rect.height = rect - .height - .saturating_sub(rect.height.saturating_sub(1)); + let mut rect = r; + rect.x += 1; + rect.width = + rect.width.saturating_sub(2).min(txt_len); + rect.y += rect.height.saturating_sub(1); + rect.height = rect + .height + .saturating_sub(rect.height.saturating_sub(1)); - f.render_widget(w, rect); - } - } + f.render_widget(w, rect); + } + } - Ok(()) - } + Ok(()) + } - fn can_focus_diff(&self) -> bool { - match self.focus { - Focus::WorkDir => self.index_wd.is_file_seleted(), - Focus::Stage => self.index.is_file_seleted(), - Focus::Diff => false, - } - } + fn can_focus_diff(&self) -> bool { + match self.focus { + Focus::WorkDir => self.index_wd.is_file_seleted(), + Focus::Stage => self.index.is_file_seleted(), + Focus::Diff => false, + } + } - fn is_focus_on_diff(&self) -> bool { - self.focus == Focus::Diff - } + fn is_focus_on_diff(&self) -> bool { + self.focus == Focus::Diff + } - fn switch_focus(&mut self, f: Focus) -> Result { - if self.focus != f { - self.focus = f; + fn switch_focus(&mut self, f: Focus) -> Result { + if self.focus != f { + self.focus = f; - match self.focus { - Focus::WorkDir => { - self.set_diff_target(DiffTarget::WorkingDir); - self.diff.focus(false); - } - Focus::Stage => { - self.set_diff_target(DiffTarget::Stage); - self.diff.focus(false); - } - Focus::Diff => { - self.index.focus(false); - self.index_wd.focus(false); + match self.focus { + Focus::WorkDir => { + self.set_diff_target(DiffTarget::WorkingDir); + self.diff.focus(false); + } + Focus::Stage => { + self.set_diff_target(DiffTarget::Stage); + self.diff.focus(false); + } + Focus::Diff => { + self.index.focus(false); + self.index_wd.focus(false); - self.diff.focus(true); - } - }; + self.diff.focus(true); + } + }; - self.update_diff()?; + self.update_diff()?; - return Ok(true); - } + return Ok(true); + } - Ok(false) - } + Ok(false) + } - fn set_diff_target(&mut self, target: DiffTarget) { - self.diff_target = target; - let is_stage = self.diff_target == DiffTarget::Stage; + fn set_diff_target(&mut self, target: DiffTarget) { + self.diff_target = target; + let is_stage = self.diff_target == DiffTarget::Stage; - self.index_wd.focus_select(!is_stage); - self.index.focus_select(is_stage); - } + self.index_wd.focus_select(!is_stage); + self.index.focus_select(is_stage); + } - pub fn selected_path(&self) -> Option<(String, bool)> { - let (idx, is_stage) = match self.diff_target { - DiffTarget::Stage => (&self.index, true), - DiffTarget::WorkingDir => (&self.index_wd, false), - }; + pub fn selected_path(&self) -> Option<(String, bool)> { + let (idx, is_stage) = match self.diff_target { + DiffTarget::Stage => (&self.index, true), + DiffTarget::WorkingDir => (&self.index_wd, false), + }; - if let Some(item) = idx.selection() { - if let FileTreeItemKind::File(i) = item.kind { - return Some((i.path, is_stage)); - } - } - None - } + if let Some(item) = idx.selection() { + if let FileTreeItemKind::File(i) = item.kind { + return Some((i.path, is_stage)); + } + } + None + } - /// - pub fn update(&mut self) -> Result<()> { - self.git_branch_name.lookup().map(Some).unwrap_or(None); + /// + pub fn update(&mut self) -> Result<()> { + self.git_branch_name.lookup().map(Some).unwrap_or(None); - if self.is_visible() { - self.git_diff.refresh()?; - self.git_status_workdir - .fetch(&StatusParams::new(StatusType::WorkingDir))?; - self.git_status_stage - .fetch(&StatusParams::new(StatusType::Stage))?; + if self.is_visible() { + self.git_diff.refresh()?; + self.git_status_workdir + .fetch(&StatusParams::new(StatusType::WorkingDir))?; + self.git_status_stage + .fetch(&StatusParams::new(StatusType::Stage))?; - self.branch_compare(); - } + self.branch_compare(); + } - Ok(()) - } + Ok(()) + } - /// - pub fn anything_pending(&self) -> bool { - self.git_diff.is_pending() - || self.git_status_stage.is_pending() - || self.git_status_workdir.is_pending() - } + /// + pub fn anything_pending(&self) -> bool { + self.git_diff.is_pending() + || self.git_status_stage.is_pending() + || self.git_status_workdir.is_pending() + } - /// - pub fn update_git( - &mut self, - ev: AsyncGitNotification, - ) -> Result<()> { - match ev { - AsyncGitNotification::Diff => self.update_diff()?, - AsyncGitNotification::Status => self.update_status()?, - AsyncGitNotification::Push - | AsyncGitNotification::Fetch - | AsyncGitNotification::CommitFiles => { - self.branch_compare(); - } - _ => (), - } + /// + pub fn update_git( + &mut self, + ev: AsyncGitNotification, + ) -> Result<()> { + match ev { + AsyncGitNotification::Diff => self.update_diff()?, + AsyncGitNotification::Status => self.update_status()?, + AsyncGitNotification::Push + | AsyncGitNotification::Fetch + | AsyncGitNotification::CommitFiles => { + self.branch_compare(); + } + _ => (), + } - Ok(()) - } + Ok(()) + } - fn update_status(&mut self) -> Result<()> { - let stage_status = self.git_status_stage.last()?; - self.index.set_items(&stage_status.items)?; + fn update_status(&mut self) -> Result<()> { + let stage_status = self.git_status_stage.last()?; + self.index.set_items(&stage_status.items)?; - let workdir_status = self.git_status_workdir.last()?; - self.index_wd.set_items(&workdir_status.items)?; + let workdir_status = self.git_status_workdir.last()?; + self.index_wd.set_items(&workdir_status.items)?; - self.update_diff()?; + self.update_diff()?; - if self.git_action_executed { - self.git_action_executed = false; + if self.git_action_executed { + self.git_action_executed = false; - if self.focus == Focus::WorkDir - && workdir_status.items.is_empty() - && !stage_status.items.is_empty() - { - self.switch_focus(Focus::Stage)?; - } else if self.focus == Focus::Stage - && stage_status.items.is_empty() - { - self.switch_focus(Focus::WorkDir)?; - } - } + if self.focus == Focus::WorkDir + && workdir_status.items.is_empty() + && !stage_status.items.is_empty() + { + self.switch_focus(Focus::Stage)?; + } else if self.focus == Focus::Stage + && stage_status.items.is_empty() + { + self.switch_focus(Focus::WorkDir)?; + } + } - Ok(()) - } + Ok(()) + } - /// - pub fn update_diff(&mut self) -> Result<()> { - if let Some((path, is_stage)) = self.selected_path() { - let diff_type = if is_stage { - DiffType::Stage - } else { - DiffType::WorkDir - }; + /// + pub fn update_diff(&mut self) -> Result<()> { + if let Some((path, is_stage)) = self.selected_path() { + let diff_type = if is_stage { + DiffType::Stage + } else { + DiffType::WorkDir + }; - let diff_params = DiffParams { - path: path.clone(), - diff_type, - }; + let diff_params = DiffParams { + path: path.clone(), + diff_type, + }; - if self.diff.current() == (path.clone(), is_stage) { - // we are already showing a diff of the right file - // maybe the diff changed (outside file change) - if let Some((params, last)) = self.git_diff.last()? { - if params == diff_params { - self.diff.update(path, is_stage, last); - } - } - } else { - // we dont show the right diff right now, so we need to request - if let Some(diff) = - self.git_diff.request(diff_params)? - { - self.diff.update(path, is_stage, diff); - } else { - self.diff.clear(true); - } - } - } else { - self.diff.clear(false); - } + if self.diff.current() == (path.clone(), is_stage) { + // we are already showing a diff of the right file + // maybe the diff changed (outside file change) + if let Some((params, last)) = self.git_diff.last()? { + if params == diff_params { + self.diff.update(path, is_stage, last); + } + } + } else { + // we dont show the right diff right now, so we need to request + if let Some(diff) = + self.git_diff.request(diff_params)? + { + self.diff.update(path, is_stage, diff); + } else { + self.diff.clear(true); + } + } + } else { + self.diff.clear(false); + } - Ok(()) - } + Ok(()) + } - /// called after confirmation - pub fn reset(&mut self, item: &ResetItem) -> bool { - if let Err(e) = sync::reset_workdir(CWD, item.path.as_str()) { - self.queue.push(InternalEvent::ShowErrorMsg(format!( - "reset failed:\n{}", - e - ))); + /// called after confirmation + pub fn reset(&mut self, item: &ResetItem) -> bool { + if let Err(e) = sync::reset_workdir(CWD, item.path.as_str()) { + self.queue.push(InternalEvent::ShowErrorMsg(format!( + "reset failed:\n{}", + e + ))); - false - } else { - true - } - } + false + } else { + true + } + } - pub fn last_file_moved(&mut self) -> Result<()> { - if !self.is_focus_on_diff() && self.is_visible() { - self.switch_focus(self.focus.toggled_focus())?; - } - Ok(()) - } + pub fn last_file_moved(&mut self) -> Result<()> { + if !self.is_focus_on_diff() && self.is_visible() { + self.switch_focus(self.focus.toggled_focus())?; + } + Ok(()) + } - fn push(&self, force: bool) { - if self.can_push() { - if let Some(branch) = self.git_branch_name.last() { - if force { - self.queue.push(InternalEvent::ConfirmAction( - Action::ForcePush(branch, force), - )); - } else { - self.queue.push(InternalEvent::Push( - branch, force, false, - )); - } - } - } - } + fn push(&self, force: bool) { + if self.can_push() { + if let Some(branch) = self.git_branch_name.last() { + if force { + self.queue.push(InternalEvent::ConfirmAction( + Action::ForcePush(branch, force), + )); + } else { + self.queue.push(InternalEvent::Push( + branch, force, false, + )); + } + } + } + } - fn pull(&self) { - if let Some(branch) = self.git_branch_name.last() { - self.queue.push(InternalEvent::Pull(branch)); - } - } + fn pull(&self) { + if let Some(branch) = self.git_branch_name.last() { + self.queue.push(InternalEvent::Pull(branch)); + } + } - fn undo_last_commit(&self) { - try_or_popup!( - self, - "undo commit failed:", - sync::utils::undo_last_commit(CWD) - ); - } + fn undo_last_commit(&self) { + try_or_popup!( + self, + "undo commit failed:", + sync::utils::undo_last_commit(CWD) + ); + } - fn branch_compare(&mut self) { - self.git_branch_state = - self.git_branch_name.last().and_then(|branch| { - sync::branch_compare_upstream(CWD, branch.as_str()) - .ok() - }); - } + fn branch_compare(&mut self) { + self.git_branch_state = + self.git_branch_name.last().and_then(|branch| { + sync::branch_compare_upstream(CWD, branch.as_str()) + .ok() + }); + } - fn can_push(&self) -> bool { - self.git_branch_state - .as_ref() - .map_or(true, |state| state.ahead > 0) - } + fn can_push(&self) -> bool { + self.git_branch_state + .as_ref() + .map_or(true, |state| state.ahead > 0) + } - fn can_abort_merge() -> bool { - sync::repo_state(CWD).unwrap_or(RepoState::Clean) - == RepoState::Merge - } + fn can_abort_merge() -> bool { + sync::repo_state(CWD).unwrap_or(RepoState::Clean) + == RepoState::Merge + } - pub fn abort_merge(&self) { - try_or_popup!(self, "abort merge", sync::abort_merge(CWD)); - } + pub fn abort_merge(&self) { + try_or_popup!(self, "abort merge", sync::abort_merge(CWD)); + } - fn commands_nav( - &self, - out: &mut Vec, - force_all: bool, - ) { - let focus_on_diff = self.is_focus_on_diff(); - out.push( - CommandInfo::new( - strings::commands::diff_focus_left(&self.key_config), - true, - (self.visible && focus_on_diff) || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::diff_focus_right(&self.key_config), - self.can_focus_diff(), - (self.visible && !focus_on_diff) || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::select_staging(&self.key_config), - !focus_on_diff, - (self.visible - && !focus_on_diff - && self.focus == Focus::WorkDir) - || force_all, - ) - .order(strings::order::NAV), - ); - out.push( - CommandInfo::new( - strings::commands::select_unstaged(&self.key_config), - !focus_on_diff, - (self.visible - && !focus_on_diff - && self.focus == Focus::Stage) - || force_all, - ) - .order(strings::order::NAV), - ); - } + fn commands_nav( + &self, + out: &mut Vec, + force_all: bool, + ) { + let focus_on_diff = self.is_focus_on_diff(); + out.push( + CommandInfo::new( + strings::commands::diff_focus_left(&self.key_config), + true, + (self.visible && focus_on_diff) || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::diff_focus_right(&self.key_config), + self.can_focus_diff(), + (self.visible && !focus_on_diff) || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::select_staging(&self.key_config), + !focus_on_diff, + (self.visible + && !focus_on_diff && self.focus == Focus::WorkDir) + || force_all, + ) + .order(strings::order::NAV), + ); + out.push( + CommandInfo::new( + strings::commands::select_unstaged(&self.key_config), + !focus_on_diff, + (self.visible + && !focus_on_diff && self.focus == Focus::Stage) + || force_all, + ) + .order(strings::order::NAV), + ); + } } impl Component for Status { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - let focus_on_diff = self.is_focus_on_diff(); + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + let focus_on_diff = self.is_focus_on_diff(); - if self.visible || force_all { - command_pump( - out, - force_all, - self.components().as_slice(), - ); + if self.visible || force_all { + command_pump( + out, + force_all, + self.components().as_slice(), + ); - out.push(CommandInfo::new( - strings::commands::open_branch_select_popup( - &self.key_config, - ), - true, - !focus_on_diff, - )); + out.push(CommandInfo::new( + strings::commands::open_branch_select_popup( + &self.key_config, + ), + true, + !focus_on_diff, + )); - out.push(CommandInfo::new( - strings::commands::status_push(&self.key_config), - self.can_push(), - !focus_on_diff, - )); - out.push(CommandInfo::new( - strings::commands::status_force_push( - &self.key_config, - ), - true, - self.can_push() && !focus_on_diff, - )); - out.push(CommandInfo::new( - strings::commands::status_pull(&self.key_config), - true, - !focus_on_diff, - )); + out.push(CommandInfo::new( + strings::commands::status_push(&self.key_config), + self.can_push(), + !focus_on_diff, + )); + out.push(CommandInfo::new( + strings::commands::status_force_push( + &self.key_config, + ), + true, + self.can_push() && !focus_on_diff, + )); + out.push(CommandInfo::new( + strings::commands::status_pull(&self.key_config), + true, + !focus_on_diff, + )); - out.push(CommandInfo::new( - strings::commands::undo_commit(&self.key_config), - true, - !focus_on_diff, - )); + out.push(CommandInfo::new( + strings::commands::undo_commit(&self.key_config), + true, + !focus_on_diff, + )); - out.push(CommandInfo::new( - strings::commands::abort_merge(&self.key_config), - true, - Self::can_abort_merge() || force_all, - )); - } + out.push(CommandInfo::new( + strings::commands::abort_merge(&self.key_config), + true, + Self::can_abort_merge() || force_all, + )); + } - { - out.push(CommandInfo::new( - strings::commands::edit_item(&self.key_config), - if focus_on_diff { - true - } else { - self.can_focus_diff() - }, - self.visible || force_all, - )); + { + out.push(CommandInfo::new( + strings::commands::edit_item(&self.key_config), + if focus_on_diff { + true + } else { + self.can_focus_diff() + }, + self.visible || force_all, + )); - out.push( - CommandInfo::new( - strings::commands::select_status( - &self.key_config, - ), - true, - (self.visible && !focus_on_diff) || force_all, - ) - .hidden(), - ); + out.push( + CommandInfo::new( + strings::commands::select_status( + &self.key_config, + ), + true, + (self.visible && !focus_on_diff) || force_all, + ) + .hidden(), + ); - self.commands_nav(out, force_all); - } + self.commands_nav(out, force_all); + } - visibility_blocking(self) - } + visibility_blocking(self) + } - fn event( - &mut self, - ev: crossterm::event::Event, - ) -> Result { - if self.visible { - if event_pump(ev, self.components_mut().as_mut_slice())? - .is_consumed() - { - self.git_action_executed = true; - return Ok(EventState::Consumed); - } + fn event( + &mut self, + ev: crossterm::event::Event, + ) -> Result { + if self.visible { + if event_pump(ev, self.components_mut().as_mut_slice())? + .is_consumed() + { + self.git_action_executed = true; + return Ok(EventState::Consumed); + } - if let Event::Key(k) = ev { - return if k == self.key_config.edit_file - && (self.can_focus_diff() - || self.is_focus_on_diff()) - { - if let Some((path, _)) = self.selected_path() { - self.queue.push( - InternalEvent::OpenExternalEditor(Some( - path, - )), - ); - } - Ok(EventState::Consumed) - } else if k == self.key_config.toggle_workarea - && !self.is_focus_on_diff() - { - self.switch_focus(self.focus.toggled_focus()) - .map(Into::into) - } else if k == self.key_config.focus_right - && self.can_focus_diff() - { - self.switch_focus(Focus::Diff).map(Into::into) - } else if k == self.key_config.focus_left { - self.switch_focus(match self.diff_target { - DiffTarget::Stage => Focus::Stage, - DiffTarget::WorkingDir => Focus::WorkDir, - }) - .map(Into::into) - } else if k == self.key_config.move_down - && self.focus == Focus::WorkDir - && !self.index.is_empty() - { - self.switch_focus(Focus::Stage).map(Into::into) - } else if k == self.key_config.move_up - && self.focus == Focus::Stage - && !self.index_wd.is_empty() - { - self.switch_focus(Focus::WorkDir).map(Into::into) - } else if k == self.key_config.select_branch - && !self.is_focus_on_diff() - { - self.queue.push(InternalEvent::SelectBranch); - Ok(EventState::Consumed) - } else if k == self.key_config.force_push - && !self.is_focus_on_diff() - && self.can_push() - { - self.push(true); - Ok(EventState::Consumed) - } else if k == self.key_config.push - && !self.is_focus_on_diff() - { - self.push(false); - Ok(EventState::Consumed) - } else if k == self.key_config.pull - && !self.is_focus_on_diff() - { - self.pull(); - Ok(EventState::Consumed) - } else if k == self.key_config.undo_commit - && !self.is_focus_on_diff() - { - self.undo_last_commit(); - self.queue.push(InternalEvent::Update( - NeedsUpdate::ALL, - )); - Ok(EventState::Consumed) - } else if k == self.key_config.abort_merge - && Self::can_abort_merge() - { - self.queue.push(InternalEvent::ConfirmAction( - Action::AbortMerge, - )); + if let Event::Key(k) = ev { + return if k == self.key_config.edit_file + && (self.can_focus_diff() + || self.is_focus_on_diff()) + { + if let Some((path, _)) = self.selected_path() { + self.queue.push( + InternalEvent::OpenExternalEditor(Some( + path, + )), + ); + } + Ok(EventState::Consumed) + } else if k == self.key_config.toggle_workarea + && !self.is_focus_on_diff() + { + self.switch_focus(self.focus.toggled_focus()) + .map(Into::into) + } else if k == self.key_config.focus_right + && self.can_focus_diff() + { + self.switch_focus(Focus::Diff).map(Into::into) + } else if k == self.key_config.focus_left { + self.switch_focus(match self.diff_target { + DiffTarget::Stage => Focus::Stage, + DiffTarget::WorkingDir => Focus::WorkDir, + }) + .map(Into::into) + } else if k == self.key_config.move_down + && self.focus == Focus::WorkDir + && !self.index.is_empty() + { + self.switch_focus(Focus::Stage).map(Into::into) + } else if k == self.key_config.move_up + && self.focus == Focus::Stage + && !self.index_wd.is_empty() + { + self.switch_focus(Focus::WorkDir).map(Into::into) + } else if k == self.key_config.select_branch + && !self.is_focus_on_diff() + { + self.queue.push(InternalEvent::SelectBranch); + Ok(EventState::Consumed) + } else if k == self.key_config.force_push + && !self.is_focus_on_diff() + && self.can_push() + { + self.push(true); + Ok(EventState::Consumed) + } else if k == self.key_config.push + && !self.is_focus_on_diff() + { + self.push(false); + Ok(EventState::Consumed) + } else if k == self.key_config.pull + && !self.is_focus_on_diff() + { + self.pull(); + Ok(EventState::Consumed) + } else if k == self.key_config.undo_commit + && !self.is_focus_on_diff() + { + self.undo_last_commit(); + self.queue.push(InternalEvent::Update( + NeedsUpdate::ALL, + )); + Ok(EventState::Consumed) + } else if k == self.key_config.abort_merge + && Self::can_abort_merge() + { + self.queue.push(InternalEvent::ConfirmAction( + Action::AbortMerge, + )); - Ok(EventState::Consumed) - } else { - Ok(EventState::NotConsumed) - }; - } - } + Ok(EventState::Consumed) + } else { + Ok(EventState::NotConsumed) + }; + } + } - Ok(EventState::NotConsumed) - } + Ok(EventState::NotConsumed) + } - fn is_visible(&self) -> bool { - self.visible - } + fn is_visible(&self) -> bool { + self.visible + } - fn hide(&mut self) { - self.visible = false; - } + fn hide(&mut self) { + self.visible = false; + } - fn show(&mut self) -> Result<()> { - self.visible = true; - self.update()?; + fn show(&mut self) -> Result<()> { + self.visible = true; + self.update()?; - Ok(()) - } + Ok(()) + } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ae143085..81158003 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -9,7 +9,7 @@ use filetreelist::MoveSelection; pub use scrollbar::draw_scrollbar; pub use scrolllist::{draw_list, draw_list_block}; pub use stateful_paragraph::{ - ParagraphState, ScrollPos, StatefulParagraph, + ParagraphState, ScrollPos, StatefulParagraph, }; pub use syntax_text::{AsyncSyntaxJob, SyntaxText}; use tui::layout::{Constraint, Direction, Layout, Rect}; @@ -18,123 +18,123 @@ use crate::keys::SharedKeyConfig; /// return the scroll position (line) necessary to have the `selection` in view if it is not already pub const fn calc_scroll_top( - current_top: usize, - height_in_lines: usize, - selection: usize, + current_top: usize, + height_in_lines: usize, + selection: usize, ) -> usize { - if current_top + height_in_lines <= selection { - selection.saturating_sub(height_in_lines) + 1 - } else if current_top > selection { - selection - } else { - current_top - } + if current_top + height_in_lines <= selection { + selection.saturating_sub(height_in_lines) + 1 + } else if current_top > selection { + selection + } else { + current_top + } } /// ui component size representation #[derive(Copy, Clone)] pub struct Size { - pub width: u16, - pub height: u16, + pub width: u16, + pub height: u16, } impl Size { - pub const fn new(width: u16, height: u16) -> Self { - Self { width, height } - } + pub const fn new(width: u16, height: u16) -> Self { + Self { width, height } + } } impl From for Size { - fn from(r: Rect) -> Self { - Self { - width: r.width, - height: r.height, - } - } + fn from(r: Rect) -> Self { + Self { + width: r.width, + height: r.height, + } + } } /// use layouts to create a rects that /// centers inside `r` and sizes `percent_x`/`percent_x` of `r` pub fn centered_rect( - percent_x: u16, - percent_y: u16, - r: Rect, + percent_x: u16, + percent_y: u16, + r: Rect, ) -> Rect { - let popup_layout = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage((100 - percent_y) / 2), - Constraint::Percentage(percent_y), - Constraint::Percentage((100 - percent_y) / 2), - ] - .as_ref(), - ) - .split(r); + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ] + .as_ref(), + ) + .split(r); - Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage((100 - percent_x) / 2), - Constraint::Percentage(percent_x), - Constraint::Percentage((100 - percent_x) / 2), - ] - .as_ref(), - ) - .split(popup_layout[1])[1] + Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ] + .as_ref(), + ) + .split(popup_layout[1])[1] } /// makes sure Rect `r` at least stays as big as min and not bigger than max pub fn rect_inside(min: Size, max: Size, r: Rect) -> Rect { - let new_width = r.width.max(min.width).min(max.width); - let new_height = r.height.max(min.height).min(max.height); - let diff_width = new_width.saturating_sub(r.width); - let diff_height = new_height.saturating_sub(r.height); + let new_width = r.width.max(min.width).min(max.width); + let new_height = r.height.max(min.height).min(max.height); + let diff_width = new_width.saturating_sub(r.width); + let diff_height = new_height.saturating_sub(r.height); - Rect::new( - r.x.saturating_sub(diff_width / 2), - r.y.saturating_sub(diff_height / 2), - new_width, - new_height, - ) + Rect::new( + r.x.saturating_sub(diff_width / 2), + r.y.saturating_sub(diff_height / 2), + new_width, + new_height, + ) } pub fn centered_rect_absolute( - width: u16, - height: u16, - r: Rect, + width: u16, + height: u16, + r: Rect, ) -> Rect { - Rect::new( - (r.width.saturating_sub(width)) / 2, - (r.height.saturating_sub(height)) / 2, - width.min(r.width), - height.min(r.height), - ) + Rect::new( + (r.width.saturating_sub(width)) / 2, + (r.height.saturating_sub(height)) / 2, + width.min(r.width), + height.min(r.height), + ) } /// pub fn common_nav( - key: crossterm::event::KeyEvent, - key_config: &SharedKeyConfig, + key: crossterm::event::KeyEvent, + key_config: &SharedKeyConfig, ) -> Option { - if key == key_config.move_down { - Some(MoveSelection::Down) - } else if key == key_config.move_up { - Some(MoveSelection::Up) - } else if key == key_config.page_up { - Some(MoveSelection::PageUp) - } else if key == key_config.page_down { - Some(MoveSelection::PageDown) - } else if key == key_config.move_right { - Some(MoveSelection::Right) - } else if key == key_config.move_left { - Some(MoveSelection::Left) - } else if key == key_config.home || key == key_config.shift_up { - Some(MoveSelection::Top) - } else if key == key_config.end || key == key_config.shift_down { - Some(MoveSelection::End) - } else { - None - } + if key == key_config.move_down { + Some(MoveSelection::Down) + } else if key == key_config.move_up { + Some(MoveSelection::Up) + } else if key == key_config.page_up { + Some(MoveSelection::PageUp) + } else if key == key_config.page_down { + Some(MoveSelection::PageDown) + } else if key == key_config.move_right { + Some(MoveSelection::Right) + } else if key == key_config.move_left { + Some(MoveSelection::Left) + } else if key == key_config.home || key == key_config.shift_up { + Some(MoveSelection::Top) + } else if key == key_config.end || key == key_config.shift_down { + Some(MoveSelection::End) + } else { + None + } } diff --git a/src/ui/reflow.rs b/src/ui/reflow.rs index 3d469c52..b2417867 100644 --- a/src/ui/reflow.rs +++ b/src/ui/reflow.rs @@ -9,655 +9,654 @@ const NBSP: &str = "\u{00a0}"; /// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming /// iterators for that). pub trait LineComposer<'a> { - fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)>; + fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)>; } /// A state machine that wraps lines on word boundaries. pub struct WordWrapper<'a, 'b> { - symbols: &'b mut dyn Iterator>, - max_line_width: u16, - current_line: Vec>, - next_line: Vec>, - /// Removes the leading whitespace from lines - trim: bool, + symbols: &'b mut dyn Iterator>, + max_line_width: u16, + current_line: Vec>, + next_line: Vec>, + /// Removes the leading whitespace from lines + trim: bool, } impl<'a, 'b> WordWrapper<'a, 'b> { - pub fn new( - symbols: &'b mut dyn Iterator>, - max_line_width: u16, - trim: bool, - ) -> WordWrapper<'a, 'b> { - WordWrapper { - symbols, - max_line_width, - current_line: vec![], - next_line: vec![], - trim, - } - } + pub fn new( + symbols: &'b mut dyn Iterator>, + max_line_width: u16, + trim: bool, + ) -> WordWrapper<'a, 'b> { + WordWrapper { + symbols, + max_line_width, + current_line: vec![], + next_line: vec![], + trim, + } + } } impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> { - fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { - if self.max_line_width == 0 { - return None; - } - std::mem::swap(&mut self.current_line, &mut self.next_line); - self.next_line.truncate(0); + fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { + if self.max_line_width == 0 { + return None; + } + std::mem::swap(&mut self.current_line, &mut self.next_line); + self.next_line.truncate(0); - let mut current_line_width = self - .current_line - .iter() - .map(|StyledGrapheme { symbol, .. }| -> u16 { - symbol.width().cast() - }) - .sum(); + let mut current_line_width = self + .current_line + .iter() + .map(|StyledGrapheme { symbol, .. }| -> u16 { + symbol.width().cast() + }) + .sum(); - let mut symbols_to_last_word_end: usize = 0; - let mut width_to_last_word_end: u16 = 0; - let mut prev_whitespace = false; - let mut symbols_exhausted = true; - for StyledGrapheme { symbol, style } in &mut self.symbols { - symbols_exhausted = false; - let symbol_whitespace = - symbol.chars().all(&char::is_whitespace) - && symbol != NBSP; + let mut symbols_to_last_word_end: usize = 0; + let mut width_to_last_word_end: u16 = 0; + let mut prev_whitespace = false; + let mut symbols_exhausted = true; + for StyledGrapheme { symbol, style } in &mut self.symbols { + symbols_exhausted = false; + let symbol_whitespace = + symbol.chars().all(&char::is_whitespace) + && symbol != NBSP; - // Ignore characters wider that the total max width. - if Cast::::cast(symbol.width()) > self.max_line_width + // Ignore characters wider that the total max width. + if Cast::::cast(symbol.width()) > self.max_line_width // Skip leading whitespace when trim is enabled. || self.trim && symbol_whitespace && symbol != "\n" && current_line_width == 0 - { - continue; - } + { + continue; + } - // Break on newline and discard it. - if symbol == "\n" { - if prev_whitespace { - current_line_width = width_to_last_word_end; - self.current_line - .truncate(symbols_to_last_word_end); - } - break; - } + // Break on newline and discard it. + if symbol == "\n" { + if prev_whitespace { + current_line_width = width_to_last_word_end; + self.current_line + .truncate(symbols_to_last_word_end); + } + break; + } - // Mark the previous symbol as word end. - if symbol_whitespace && !prev_whitespace { - symbols_to_last_word_end = self.current_line.len(); - width_to_last_word_end = current_line_width; - } + // Mark the previous symbol as word end. + if symbol_whitespace && !prev_whitespace { + symbols_to_last_word_end = self.current_line.len(); + width_to_last_word_end = current_line_width; + } - self.current_line.push(StyledGrapheme { symbol, style }); - current_line_width += Cast::::cast(symbol.width()); + self.current_line.push(StyledGrapheme { symbol, style }); + current_line_width += Cast::::cast(symbol.width()); - if current_line_width > self.max_line_width { - // If there was no word break in the text, wrap at the end of the line. - let (truncate_at, truncated_width) = - if symbols_to_last_word_end == 0 { - ( - self.current_line.len() - 1, - self.max_line_width, - ) - } else { - ( - symbols_to_last_word_end, - width_to_last_word_end, - ) - }; + if current_line_width > self.max_line_width { + // If there was no word break in the text, wrap at the end of the line. + let (truncate_at, truncated_width) = + if symbols_to_last_word_end == 0 { + ( + self.current_line.len() - 1, + self.max_line_width, + ) + } else { + ( + symbols_to_last_word_end, + width_to_last_word_end, + ) + }; - // Push the remainder to the next line but strip leading whitespace: - { - let remainder = &self.current_line[truncate_at..]; - if let Some(remainder_nonwhite) = - remainder.iter().position( - |StyledGrapheme { symbol, .. }| { - !symbol - .chars() - .all(&char::is_whitespace) - }, - ) - { - self.next_line.extend_from_slice( - &remainder[remainder_nonwhite..], - ); - } - } - self.current_line.truncate(truncate_at); - current_line_width = truncated_width; - break; - } + // Push the remainder to the next line but strip leading whitespace: + { + let remainder = &self.current_line[truncate_at..]; + if let Some(remainder_nonwhite) = + remainder.iter().position( + |StyledGrapheme { symbol, .. }| { + !symbol + .chars() + .all(&char::is_whitespace) + }, + ) { + self.next_line.extend_from_slice( + &remainder[remainder_nonwhite..], + ); + } + } + self.current_line.truncate(truncate_at); + current_line_width = truncated_width; + break; + } - prev_whitespace = symbol_whitespace; - } + prev_whitespace = symbol_whitespace; + } - // Even if the iterator is exhausted, pass the previous remainder. - if symbols_exhausted && self.current_line.is_empty() { - None - } else { - Some((&self.current_line[..], current_line_width)) - } - } + // Even if the iterator is exhausted, pass the previous remainder. + if symbols_exhausted && self.current_line.is_empty() { + None + } else { + Some((&self.current_line[..], current_line_width)) + } + } } /// A state machine that truncates overhanging lines. pub struct LineTruncator<'a, 'b> { - symbols: &'b mut dyn Iterator>, - max_line_width: u16, - current_line: Vec>, - /// Record the offet to skip render - horizontal_offset: u16, + symbols: &'b mut dyn Iterator>, + max_line_width: u16, + current_line: Vec>, + /// Record the offet to skip render + horizontal_offset: u16, } impl<'a, 'b> LineTruncator<'a, 'b> { - pub fn new( - symbols: &'b mut dyn Iterator>, - max_line_width: u16, - ) -> LineTruncator<'a, 'b> { - LineTruncator { - symbols, - max_line_width, - horizontal_offset: 0, - current_line: vec![], - } - } + pub fn new( + symbols: &'b mut dyn Iterator>, + max_line_width: u16, + ) -> LineTruncator<'a, 'b> { + LineTruncator { + symbols, + max_line_width, + horizontal_offset: 0, + current_line: vec![], + } + } - pub fn set_horizontal_offset(&mut self, horizontal_offset: u16) { - self.horizontal_offset = horizontal_offset; - } + pub fn set_horizontal_offset(&mut self, horizontal_offset: u16) { + self.horizontal_offset = horizontal_offset; + } } impl<'a, 'b> LineComposer<'a> for LineTruncator<'a, 'b> { - fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { - if self.max_line_width == 0 { - return None; - } + fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { + if self.max_line_width == 0 { + return None; + } - self.current_line.truncate(0); - let mut current_line_width = 0; + self.current_line.truncate(0); + let mut current_line_width = 0; - let mut skip_rest = false; - let mut symbols_exhausted = true; - let mut horizontal_offset = self.horizontal_offset as usize; - for StyledGrapheme { symbol, style } in &mut self.symbols { - symbols_exhausted = false; + let mut skip_rest = false; + let mut symbols_exhausted = true; + let mut horizontal_offset = self.horizontal_offset as usize; + for StyledGrapheme { symbol, style } in &mut self.symbols { + symbols_exhausted = false; - // Ignore characters wider that the total max width. - if Cast::::cast(symbol.width()) > self.max_line_width - { - continue; - } + // Ignore characters wider that the total max width. + if Cast::::cast(symbol.width()) > self.max_line_width + { + continue; + } - // Break on newline and discard it. - if symbol == "\n" { - break; - } + // Break on newline and discard it. + if symbol == "\n" { + break; + } - if current_line_width + Cast::::cast(symbol.width()) - > self.max_line_width - { - // Exhaust the remainder of the line. - skip_rest = true; - break; - } + if current_line_width + Cast::::cast(symbol.width()) + > self.max_line_width + { + // Exhaust the remainder of the line. + skip_rest = true; + break; + } - let symbol = if horizontal_offset == 0 { - symbol - } else { - let w = symbol.width(); - if w > horizontal_offset { - let t = trim_offset(symbol, horizontal_offset); - horizontal_offset = 0; - t - } else { - horizontal_offset -= w; - "" - } - }; - current_line_width += Cast::::cast(symbol.width()); - self.current_line.push(StyledGrapheme { symbol, style }); - } + let symbol = if horizontal_offset == 0 { + symbol + } else { + let w = symbol.width(); + if w > horizontal_offset { + let t = trim_offset(symbol, horizontal_offset); + horizontal_offset = 0; + t + } else { + horizontal_offset -= w; + "" + } + }; + current_line_width += Cast::::cast(symbol.width()); + self.current_line.push(StyledGrapheme { symbol, style }); + } - if skip_rest { - for StyledGrapheme { symbol, .. } in &mut self.symbols { - if symbol == "\n" { - break; - } - } - } + if skip_rest { + for StyledGrapheme { symbol, .. } in &mut self.symbols { + if symbol == "\n" { + break; + } + } + } - if symbols_exhausted && self.current_line.is_empty() { - None - } else { - Some((&self.current_line[..], current_line_width)) - } - } + if symbols_exhausted && self.current_line.is_empty() { + None + } else { + Some((&self.current_line[..], current_line_width)) + } + } } /// This function will return a str slice which start at specified offset. /// As src is a unicode str, start offset has to be calculated with each character. fn trim_offset(src: &str, mut offset: usize) -> &str { - let mut start = 0; - for c in UnicodeSegmentation::graphemes(src, true) { - let w = c.width(); - if w <= offset { - offset -= w; - start += c.len(); - } else { - break; - } - } - &src[start..] + let mut start = 0; + for c in UnicodeSegmentation::graphemes(src, true) { + let w = c.width(); + if w <= offset { + offset -= w; + start += c.len(); + } else { + break; + } + } + &src[start..] } #[cfg(test)] mod test { - use super::*; - use unicode_segmentation::UnicodeSegmentation; + use super::*; + use unicode_segmentation::UnicodeSegmentation; - enum Composer { - WordWrapper { trim: bool }, - LineTruncator, - } + enum Composer { + WordWrapper { trim: bool }, + LineTruncator, + } - fn run_composer( - which: Composer, - text: &str, - text_area_width: u16, - ) -> (Vec, Vec) { - let style = Default::default(); - let mut styled = UnicodeSegmentation::graphemes(text, true) - .map(|g| StyledGrapheme { symbol: g, style }); - let mut composer: Box = match which { - Composer::WordWrapper { trim } => Box::new( - WordWrapper::new(&mut styled, text_area_width, trim), - ), - Composer::LineTruncator => Box::new(LineTruncator::new( - &mut styled, - text_area_width, - )), - }; - let mut lines = vec![]; - let mut widths = vec![]; - while let Some((styled, width)) = composer.next_line() { - let line = styled - .iter() - .map(|StyledGrapheme { symbol, .. }| *symbol) - .collect::(); - assert!(width <= text_area_width); - lines.push(line); - widths.push(width); - } - (lines, widths) - } + fn run_composer( + which: Composer, + text: &str, + text_area_width: u16, + ) -> (Vec, Vec) { + let style = Default::default(); + let mut styled = UnicodeSegmentation::graphemes(text, true) + .map(|g| StyledGrapheme { symbol: g, style }); + let mut composer: Box = match which { + Composer::WordWrapper { trim } => Box::new( + WordWrapper::new(&mut styled, text_area_width, trim), + ), + Composer::LineTruncator => Box::new(LineTruncator::new( + &mut styled, + text_area_width, + )), + }; + let mut lines = vec![]; + let mut widths = vec![]; + while let Some((styled, width)) = composer.next_line() { + let line = styled + .iter() + .map(|StyledGrapheme { symbol, .. }| *symbol) + .collect::(); + assert!(width <= text_area_width); + lines.push(line); + widths.push(width); + } + (lines, widths) + } - #[test] - fn line_composer_one_line() { - let width = 40; - for i in 1..width { - let text = "a".repeat(i); - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - &text, - width as u16, - ); - let (line_truncator, _) = run_composer( - Composer::LineTruncator, - &text, - width as u16, - ); - let expected = vec![text]; - assert_eq!(word_wrapper, expected); - assert_eq!(line_truncator, expected); - } - } + #[test] + fn line_composer_one_line() { + let width = 40; + for i in 1..width { + let text = "a".repeat(i); + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + &text, + width as u16, + ); + let (line_truncator, _) = run_composer( + Composer::LineTruncator, + &text, + width as u16, + ); + let expected = vec![text]; + assert_eq!(word_wrapper, expected); + assert_eq!(line_truncator, expected); + } + } - #[test] - fn line_composer_short_lines() { - let width = 20; - let text = + #[test] + fn line_composer_short_lines() { + let width = 20; + let text = "abcdefg\nhijklmno\npabcdefg\nhijklmn\nopabcdefghijk\nlmnopabcd\n\n\nefghijklmno"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); - let wrapped: Vec<&str> = text.split('\n').collect(); - assert_eq!(word_wrapper, wrapped); - assert_eq!(line_truncator, wrapped); - } + let wrapped: Vec<&str> = text.split('\n').collect(); + assert_eq!(word_wrapper, wrapped); + assert_eq!(line_truncator, wrapped); + } - #[test] - fn line_composer_long_word() { - let width = 20; - let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width as u16, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width as u16); + #[test] + fn line_composer_long_word() { + let width = 20; + let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width as u16, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width as u16); - let wrapped = vec![ - &text[..width], - &text[width..width * 2], - &text[width * 2..width * 3], - &text[width * 3..], - ]; - assert_eq!( + let wrapped = vec![ + &text[..width], + &text[width..width * 2], + &text[width * 2..width * 3], + &text[width * 3..], + ]; + assert_eq!( word_wrapper, wrapped, "WordWrapper should detect the line cannot be broken on word boundary and \ break it at line width limit." ); - assert_eq!(line_truncator, vec![&text[..width]]); - } + assert_eq!(line_truncator, vec![&text[..width]]); + } - #[test] - fn line_composer_long_sentence() { - let width = 20; - let text = + #[test] + fn line_composer_long_sentence() { + let width = 20; + let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l m n o"; - let text_multi_space = + let text_multi_space = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l \ m n o"; - let (word_wrapper_single_space, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width as u16, - ); - let (word_wrapper_multi_space, _) = run_composer( - Composer::WordWrapper { trim: true }, - text_multi_space, - width as u16, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width as u16); + let (word_wrapper_single_space, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width as u16, + ); + let (word_wrapper_multi_space, _) = run_composer( + Composer::WordWrapper { trim: true }, + text_multi_space, + width as u16, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width as u16); - let word_wrapped = vec![ - "abcd efghij", - "klmnopabcd efgh", - "ijklmnopabcdefg", - "hijkl mnopab c d e f", - "g h i j k l m n o", - ]; - assert_eq!(word_wrapper_single_space, word_wrapped); - assert_eq!(word_wrapper_multi_space, word_wrapped); + let word_wrapped = vec![ + "abcd efghij", + "klmnopabcd efgh", + "ijklmnopabcdefg", + "hijkl mnopab c d e f", + "g h i j k l m n o", + ]; + assert_eq!(word_wrapper_single_space, word_wrapped); + assert_eq!(word_wrapper_multi_space, word_wrapped); - assert_eq!(line_truncator, vec![&text[..width]]); - } + assert_eq!(line_truncator, vec![&text[..width]]); + } - #[test] - fn line_composer_zero_width() { - let width = 0; - let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab "; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); + #[test] + fn line_composer_zero_width() { + let width = 0; + let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab "; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); - let expected: Vec<&str> = Vec::new(); - assert_eq!(word_wrapper, expected); - assert_eq!(line_truncator, expected); - } + let expected: Vec<&str> = Vec::new(); + assert_eq!(word_wrapper, expected); + assert_eq!(line_truncator, expected); + } - #[test] - fn line_composer_max_line_width_of_1() { - let width = 1; - let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab "; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); + #[test] + fn line_composer_max_line_width_of_1() { + let width = 1; + let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab "; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); - let expected: Vec<&str> = - UnicodeSegmentation::graphemes(text, true) - .filter(|g| g.chars().any(|c| !c.is_whitespace())) - .collect(); - assert_eq!(word_wrapper, expected); - assert_eq!(line_truncator, vec!["a"]); - } + let expected: Vec<&str> = + UnicodeSegmentation::graphemes(text, true) + .filter(|g| g.chars().any(|c| !c.is_whitespace())) + .collect(); + assert_eq!(word_wrapper, expected); + assert_eq!(line_truncator, vec!["a"]); + } - #[test] - fn line_composer_max_line_width_of_1_double_width_characters() { - let width = 1; - let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\ + #[test] + fn line_composer_max_line_width_of_1_double_width_characters() { + let width = 1; + let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\ 両端点では、"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); - assert_eq!(word_wrapper, vec!["", "a", "a", "a"]); - assert_eq!(line_truncator, vec!["", "a"]); - } + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); + assert_eq!(word_wrapper, vec!["", "a", "a", "a"]); + assert_eq!(line_truncator, vec!["", "a"]); + } - /// Tests WordWrapper with words some of which exceed line length and some not. - #[test] - fn line_composer_word_wrapper_mixed_length() { - let width = 20; - let text = "abcd efghij klmnopabcdefghijklmnopabcdefghijkl mnopab cdefghi j klmno"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - assert_eq!( - word_wrapper, - vec![ - "abcd efghij", - "klmnopabcdefghijklmn", - "opabcdefghijkl", - "mnopab cdefghi j", - "klmno", - ] - ) - } + /// Tests WordWrapper with words some of which exceed line length and some not. + #[test] + fn line_composer_word_wrapper_mixed_length() { + let width = 20; + let text = "abcd efghij klmnopabcdefghijklmnopabcdefghijkl mnopab cdefghi j klmno"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + assert_eq!( + word_wrapper, + vec![ + "abcd efghij", + "klmnopabcdefghijklmn", + "opabcdefghijkl", + "mnopab cdefghi j", + "klmno", + ] + ) + } - #[test] - fn line_composer_double_width_chars() { - let width = 20; - let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\ + #[test] + fn line_composer_double_width_chars() { + let width = 20; + let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\ では、"; - let (word_wrapper, word_wrapper_width) = run_composer( - Composer::WordWrapper { trim: true }, - &text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, &text, width); - assert_eq!(line_truncator, vec!["コンピュータ上で文字"]); - let wrapped = vec![ - "コンピュータ上で文字", - "を扱う場合、典型的に", - "は文字による通信を行", - "う場合にその両端点で", - "は、", - ]; - assert_eq!(word_wrapper, wrapped); - assert_eq!( - word_wrapper_width, - vec![width, width, width, width, 4] - ); - } + let (word_wrapper, word_wrapper_width) = run_composer( + Composer::WordWrapper { trim: true }, + &text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, &text, width); + assert_eq!(line_truncator, vec!["コンピュータ上で文字"]); + let wrapped = vec![ + "コンピュータ上で文字", + "を扱う場合、典型的に", + "は文字による通信を行", + "う場合にその両端点で", + "は、", + ]; + assert_eq!(word_wrapper, wrapped); + assert_eq!( + word_wrapper_width, + vec![width, width, width, width, 4] + ); + } - #[test] - fn line_composer_leading_whitespace_removal() { - let width = 20; - let text = "AAAAAAAAAAAAAAAAAAAA AAA"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); - assert_eq!( - word_wrapper, - vec!["AAAAAAAAAAAAAAAAAAAA", "AAA",] - ); - assert_eq!(line_truncator, vec!["AAAAAAAAAAAAAAAAAAAA"]); - } + #[test] + fn line_composer_leading_whitespace_removal() { + let width = 20; + let text = "AAAAAAAAAAAAAAAAAAAA AAA"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); + assert_eq!( + word_wrapper, + vec!["AAAAAAAAAAAAAAAAAAAA", "AAA",] + ); + assert_eq!(line_truncator, vec!["AAAAAAAAAAAAAAAAAAAA"]); + } - /// Tests truncation of leading whitespace. - #[test] - fn line_composer_lots_of_spaces() { - let width = 20; - let text = " "; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); - assert_eq!(word_wrapper, vec![""]); - assert_eq!(line_truncator, vec![" "]); - } + /// Tests truncation of leading whitespace. + #[test] + fn line_composer_lots_of_spaces() { + let width = 20; + let text = " "; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); + assert_eq!(word_wrapper, vec![""]); + assert_eq!(line_truncator, vec![" "]); + } - /// Tests an input starting with a letter, folowed by spaces - some of the behaviour is - /// incidental. - #[test] - fn line_composer_char_plus_lots_of_spaces() { - let width = 20; - let text = "a "; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); - // What's happening below is: the first line gets consumed, trailing spaces discarded, - // after 20 of which a word break occurs (probably shouldn't). The second line break - // discards all whitespace. The result should probably be vec!["a"] but it doesn't matter - // that much. - assert_eq!(word_wrapper, vec!["a", ""]); - assert_eq!(line_truncator, vec!["a "]); - } + /// Tests an input starting with a letter, folowed by spaces - some of the behaviour is + /// incidental. + #[test] + fn line_composer_char_plus_lots_of_spaces() { + let width = 20; + let text = "a "; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); + // What's happening below is: the first line gets consumed, trailing spaces discarded, + // after 20 of which a word break occurs (probably shouldn't). The second line break + // discards all whitespace. The result should probably be vec!["a"] but it doesn't matter + // that much. + assert_eq!(word_wrapper, vec!["a", ""]); + assert_eq!(line_truncator, vec!["a "]); + } - #[test] - fn line_composer_word_wrapper_double_width_chars_mixed_with_spaces( - ) { - let width = 20; - // Japanese seems not to use spaces but we should break on spaces anyway... We're using it - // to test double-width chars. - // You are more than welcome to add word boundary detection based of alterations of - // hiragana and katakana... - // This happens to also be a test case for mixed width because regular spaces are single width. - let text = "コンピュ ータ上で文字を扱う場合、 典型的には文 字による 通信を行 う場合にその両端点では、"; - let (word_wrapper, word_wrapper_width) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - assert_eq!( - word_wrapper, - vec![ - "コンピュ", - "ータ上で文字を扱う場", - "合、 典型的には文", - "字による 通信を行", - "う場合にその両端点で", - "は、", - ] - ); - // Odd-sized lines have a space in them. - assert_eq!(word_wrapper_width, vec![8, 20, 17, 17, 20, 4]); - } + #[test] + fn line_composer_word_wrapper_double_width_chars_mixed_with_spaces( + ) { + let width = 20; + // Japanese seems not to use spaces but we should break on spaces anyway... We're using it + // to test double-width chars. + // You are more than welcome to add word boundary detection based of alterations of + // hiragana and katakana... + // This happens to also be a test case for mixed width because regular spaces are single width. + let text = "コンピュ ータ上で文字を扱う場合、 典型的には文 字による 通信を行 う場合にその両端点では、"; + let (word_wrapper, word_wrapper_width) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + assert_eq!( + word_wrapper, + vec![ + "コンピュ", + "ータ上で文字を扱う場", + "合、 典型的には文", + "字による 通信を行", + "う場合にその両端点で", + "は、", + ] + ); + // Odd-sized lines have a space in them. + assert_eq!(word_wrapper_width, vec![8, 20, 17, 17, 20, 4]); + } - /// Ensure words separated by nbsp are wrapped as if they were a single one. - #[test] - fn line_composer_word_wrapper_nbsp() { - let width = 20; - let text = "AAAAAAAAAAAAAAA AAAA\u{00a0}AAA"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: true }, - text, - width, - ); - assert_eq!( - word_wrapper, - vec!["AAAAAAAAAAAAAAA", "AAAA\u{00a0}AAA",] - ); + /// Ensure words separated by nbsp are wrapped as if they were a single one. + #[test] + fn line_composer_word_wrapper_nbsp() { + let width = 20; + let text = "AAAAAAAAAAAAAAA AAAA\u{00a0}AAA"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: true }, + text, + width, + ); + assert_eq!( + word_wrapper, + vec!["AAAAAAAAAAAAAAA", "AAAA\u{00a0}AAA",] + ); - // Ensure that if the character was a regular space, it would be wrapped differently. - let text_space = text.replace("\u{00a0}", " "); - let (word_wrapper_space, _) = run_composer( - Composer::WordWrapper { trim: true }, - &text_space, - width, - ); - assert_eq!( - word_wrapper_space, - vec!["AAAAAAAAAAAAAAA AAAA", "AAA",] - ); - } + // Ensure that if the character was a regular space, it would be wrapped differently. + let text_space = text.replace("\u{00a0}", " "); + let (word_wrapper_space, _) = run_composer( + Composer::WordWrapper { trim: true }, + &text_space, + width, + ); + assert_eq!( + word_wrapper_space, + vec!["AAAAAAAAAAAAAAA AAAA", "AAA",] + ); + } - #[test] - fn line_composer_word_wrapper_preserve_indentation() { - let width = 20; - let text = "AAAAAAAAAAAAAAAAAAAA AAA"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: false }, - text, - width, - ); - assert_eq!( - word_wrapper, - vec!["AAAAAAAAAAAAAAAAAAAA", " AAA",] - ); - } + #[test] + fn line_composer_word_wrapper_preserve_indentation() { + let width = 20; + let text = "AAAAAAAAAAAAAAAAAAAA AAA"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: false }, + text, + width, + ); + assert_eq!( + word_wrapper, + vec!["AAAAAAAAAAAAAAAAAAAA", " AAA",] + ); + } - #[test] - fn line_composer_word_wrapper_preserve_indentation_with_wrap() { - let width = 10; - let text = "AAA AAA AAAAA AA AAAAAA\n B\n C\n D"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: false }, - text, - width, - ); - assert_eq!( - word_wrapper, - vec![ - "AAA AAA", "AAAAA AA", "AAAAAA", " B", " C", " D" - ] - ); - } + #[test] + fn line_composer_word_wrapper_preserve_indentation_with_wrap() { + let width = 10; + let text = "AAA AAA AAAAA AA AAAAAA\n B\n C\n D"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: false }, + text, + width, + ); + assert_eq!( + word_wrapper, + vec![ + "AAA AAA", "AAAAA AA", "AAAAAA", " B", " C", " D" + ] + ); + } - #[test] - fn line_composer_word_wrapper_preserve_indentation_lots_of_whitespace( - ) { - let width = 10; - let text = - " 4 Indent\n must wrap!"; - let (word_wrapper, _) = run_composer( - Composer::WordWrapper { trim: false }, - text, - width, - ); - assert_eq!( - word_wrapper, - vec![ - " ", - " 4", - "Indent", - " ", - " must", - "wrap!" - ] - ); - } + #[test] + fn line_composer_word_wrapper_preserve_indentation_lots_of_whitespace( + ) { + let width = 10; + let text = + " 4 Indent\n must wrap!"; + let (word_wrapper, _) = run_composer( + Composer::WordWrapper { trim: false }, + text, + width, + ); + assert_eq!( + word_wrapper, + vec![ + " ", + " 4", + "Indent", + " ", + " must", + "wrap!" + ] + ); + } } diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs index a1184217..adf4dec1 100644 --- a/src/ui/scrollbar.rs +++ b/src/ui/scrollbar.rs @@ -2,81 +2,81 @@ use super::style::SharedTheme; use easy_cast::CastFloat; use std::convert::TryFrom; use tui::{ - backend::Backend, - buffer::Buffer, - layout::{Margin, Rect}, - style::Style, - symbols::{block::FULL, line::DOUBLE_VERTICAL}, - widgets::Widget, - Frame, + backend::Backend, + buffer::Buffer, + layout::{Margin, Rect}, + style::Style, + symbols::{block::FULL, line::DOUBLE_VERTICAL}, + widgets::Widget, + Frame, }; /// struct Scrollbar { - max: u16, - pos: u16, - style_bar: Style, - style_pos: Style, + max: u16, + pos: u16, + style_bar: Style, + style_pos: Style, } impl Scrollbar { - fn new(max: usize, pos: usize) -> Self { - Self { - max: u16::try_from(max).unwrap_or_default(), - pos: u16::try_from(pos).unwrap_or_default(), - style_pos: Style::default(), - style_bar: Style::default(), - } - } + fn new(max: usize, pos: usize) -> Self { + Self { + max: u16::try_from(max).unwrap_or_default(), + pos: u16::try_from(pos).unwrap_or_default(), + style_pos: Style::default(), + style_bar: Style::default(), + } + } } impl Widget for Scrollbar { - fn render(self, area: Rect, buf: &mut Buffer) { - if area.height <= 2 { - return; - } + fn render(self, area: Rect, buf: &mut Buffer) { + if area.height <= 2 { + return; + } - if self.max == 0 { - return; - } + if self.max == 0 { + return; + } - let right = area.right().saturating_sub(1); - if right <= area.left() { - return; - }; + let right = area.right().saturating_sub(1); + if right <= area.left() { + return; + }; - let (bar_top, bar_height) = { - let scrollbar_area = area.inner(&Margin { - horizontal: 0, - vertical: 1, - }); + let (bar_top, bar_height) = { + let scrollbar_area = area.inner(&Margin { + horizontal: 0, + vertical: 1, + }); - (scrollbar_area.top(), scrollbar_area.height) - }; + (scrollbar_area.top(), scrollbar_area.height) + }; - for y in bar_top..(bar_top + bar_height) { - buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar); - } + for y in bar_top..(bar_top + bar_height) { + buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar); + } - let progress = f32::from(self.pos) / f32::from(self.max); - let progress = if progress > 1.0 { 1.0 } else { progress }; - let pos = f32::from(bar_height) * progress; + let progress = f32::from(self.pos) / f32::from(self.max); + let progress = if progress > 1.0 { 1.0 } else { progress }; + let pos = f32::from(bar_height) * progress; - let pos: u16 = pos.cast_nearest(); - let pos = pos.saturating_sub(1); + let pos: u16 = pos.cast_nearest(); + let pos = pos.saturating_sub(1); - buf.set_string(right, bar_top + pos, FULL, self.style_pos); - } + buf.set_string(right, bar_top + pos, FULL, self.style_pos); + } } pub fn draw_scrollbar( - f: &mut Frame, - r: Rect, - theme: &SharedTheme, - max: usize, - pos: usize, + f: &mut Frame, + r: Rect, + theme: &SharedTheme, + max: usize, + pos: usize, ) { - let mut widget = Scrollbar::new(max, pos); - widget.style_pos = theme.scroll_bar_pos(); - f.render_widget(widget, r); + let mut widget = Scrollbar::new(max, pos); + widget.style_pos = theme.scroll_bar_pos(); + f.render_widget(widget, r); } diff --git a/src/ui/scrolllist.rs b/src/ui/scrolllist.rs index 6fe8017e..adc04bb3 100644 --- a/src/ui/scrolllist.rs +++ b/src/ui/scrolllist.rs @@ -1,87 +1,87 @@ use super::style::SharedTheme; use std::iter::Iterator; use tui::{ - backend::Backend, - buffer::Buffer, - layout::Rect, - style::Style, - text::Span, - widgets::{Block, Borders, List, ListItem, Widget}, - Frame, + backend::Backend, + buffer::Buffer, + layout::Rect, + style::Style, + text::Span, + widgets::{Block, Borders, List, ListItem, Widget}, + Frame, }; /// struct ScrollableList<'b, L> where - L: Iterator>, + L: Iterator>, { - block: Option>, - /// Items to be displayed - items: L, - /// Base style of the widget - style: Style, + block: Option>, + /// Items to be displayed + items: L, + /// Base style of the widget + style: Style, } impl<'b, L> ScrollableList<'b, L> where - L: Iterator>, + L: Iterator>, { - fn new(items: L) -> Self { - Self { - block: None, - items, - style: Style::default(), - } - } + fn new(items: L) -> Self { + Self { + block: None, + items, + style: Style::default(), + } + } - fn block(mut self, block: Block<'b>) -> Self { - self.block = Some(block); - self - } + fn block(mut self, block: Block<'b>) -> Self { + self.block = Some(block); + self + } } impl<'b, L> Widget for ScrollableList<'b, L> where - L: Iterator>, + L: Iterator>, { - fn render(self, area: Rect, buf: &mut Buffer) { - // Render items - List::new( - self.items.map(ListItem::new).collect::>(), - ) - .block(self.block.unwrap_or_default()) - .style(self.style) - .render(area, buf); - } + fn render(self, area: Rect, buf: &mut Buffer) { + // Render items + List::new( + self.items.map(ListItem::new).collect::>(), + ) + .block(self.block.unwrap_or_default()) + .style(self.style) + .render(area, buf); + } } pub fn draw_list<'b, B: Backend, L>( - f: &mut Frame, - r: Rect, - title: &'b str, - items: L, - selected: bool, - theme: &SharedTheme, + f: &mut Frame, + r: Rect, + title: &'b str, + items: L, + selected: bool, + theme: &SharedTheme, ) where - L: Iterator>, + L: Iterator>, { - let list = ScrollableList::new(items).block( - Block::default() - .title(Span::styled(title, theme.title(selected))) - .borders(Borders::ALL) - .border_style(theme.block(selected)), - ); - f.render_widget(list, r); + let list = ScrollableList::new(items).block( + Block::default() + .title(Span::styled(title, theme.title(selected))) + .borders(Borders::ALL) + .border_style(theme.block(selected)), + ); + f.render_widget(list, r); } pub fn draw_list_block<'b, B: Backend, L>( - f: &mut Frame, - r: Rect, - block: Block<'b>, - items: L, + f: &mut Frame, + r: Rect, + block: Block<'b>, + items: L, ) where - L: Iterator>, + L: Iterator>, { - let list = ScrollableList::new(items).block(block); - f.render_widget(list, r); + let list = ScrollableList::new(items).block(block); + f.render_widget(list, r); } diff --git a/src/ui/stateful_paragraph.rs b/src/ui/stateful_paragraph.rs index f785c60b..49b7a22f 100644 --- a/src/ui/stateful_paragraph.rs +++ b/src/ui/stateful_paragraph.rs @@ -3,211 +3,211 @@ use easy_cast::Cast; use std::iter; use tui::{ - buffer::Buffer, - layout::{Alignment, Rect}, - style::Style, - text::{StyledGrapheme, Text}, - widgets::{Block, StatefulWidget, Widget, Wrap}, + buffer::Buffer, + layout::{Alignment, Rect}, + style::Style, + text::{StyledGrapheme, Text}, + widgets::{Block, StatefulWidget, Widget, Wrap}, }; use unicode_width::UnicodeWidthStr; use super::reflow::{LineComposer, LineTruncator, WordWrapper}; const fn get_line_offset( - line_width: u16, - text_area_width: u16, - alignment: Alignment, + line_width: u16, + text_area_width: u16, + alignment: Alignment, ) -> u16 { - match alignment { - Alignment::Center => { - (text_area_width / 2).saturating_sub(line_width / 2) - } - Alignment::Right => { - text_area_width.saturating_sub(line_width) - } - Alignment::Left => 0, - } + match alignment { + Alignment::Center => { + (text_area_width / 2).saturating_sub(line_width / 2) + } + Alignment::Right => { + text_area_width.saturating_sub(line_width) + } + Alignment::Left => 0, + } } #[derive(Debug, Clone)] pub struct StatefulParagraph<'a> { - /// A block to wrap the widget in - block: Option>, - /// Widget style - style: Style, - /// How to wrap the text - wrap: Option, - /// The text to display - text: Text<'a>, - /// Alignment of the text - alignment: Alignment, + /// A block to wrap the widget in + block: Option>, + /// Widget style + style: Style, + /// How to wrap the text + wrap: Option, + /// The text to display + text: Text<'a>, + /// Alignment of the text + alignment: Alignment, } #[derive(Debug, Default, Clone, Copy)] pub struct ScrollPos { - pub x: u16, - pub y: u16, + pub x: u16, + pub y: u16, } impl ScrollPos { - pub const fn new(x: u16, y: u16) -> Self { - Self { x, y } - } + pub const fn new(x: u16, y: u16) -> Self { + Self { x, y } + } } #[derive(Debug, Copy, Clone, Default)] pub struct ParagraphState { - /// Scroll - scroll: ScrollPos, - /// after all wrapping this is the amount of lines - lines: u16, - /// last visible height - height: u16, + /// Scroll + scroll: ScrollPos, + /// after all wrapping this is the amount of lines + lines: u16, + /// last visible height + height: u16, } impl ParagraphState { - pub const fn lines(self) -> u16 { - self.lines - } + pub const fn lines(self) -> u16 { + self.lines + } - pub const fn height(self) -> u16 { - self.height - } + pub const fn height(self) -> u16 { + self.height + } - pub const fn scroll(self) -> ScrollPos { - self.scroll - } + pub const fn scroll(self) -> ScrollPos { + self.scroll + } - pub fn set_scroll(&mut self, scroll: ScrollPos) { - self.scroll = scroll; - } + pub fn set_scroll(&mut self, scroll: ScrollPos) { + self.scroll = scroll; + } } impl<'a> StatefulParagraph<'a> { - pub fn new(text: T) -> Self - where - T: Into>, - { - Self { - block: None, - style: Style::default(), - wrap: None, - text: text.into(), - alignment: Alignment::Left, - } - } + pub fn new(text: T) -> Self + where + T: Into>, + { + Self { + block: None, + style: Style::default(), + wrap: None, + text: text.into(), + alignment: Alignment::Left, + } + } - #[allow(clippy::missing_const_for_fn)] - pub fn block(mut self, block: Block<'a>) -> Self { - self.block = Some(block); - self - } + #[allow(clippy::missing_const_for_fn)] + pub fn block(mut self, block: Block<'a>) -> Self { + self.block = Some(block); + self + } - pub const fn style(mut self, style: Style) -> Self { - self.style = style; - self - } + pub const fn style(mut self, style: Style) -> Self { + self.style = style; + self + } - pub const fn wrap(mut self, wrap: Wrap) -> Self { - self.wrap = Some(wrap); - self - } + pub const fn wrap(mut self, wrap: Wrap) -> Self { + self.wrap = Some(wrap); + self + } - pub const fn alignment(mut self, alignment: Alignment) -> Self { - self.alignment = alignment; - self - } + pub const fn alignment(mut self, alignment: Alignment) -> Self { + self.alignment = alignment; + self + } } impl<'a> StatefulWidget for StatefulParagraph<'a> { - type State = ParagraphState; + type State = ParagraphState; - fn render( - mut self, - area: Rect, - buf: &mut Buffer, - state: &mut Self::State, - ) { - buf.set_style(area, self.style); - let text_area = match self.block.take() { - Some(b) => { - let inner_area = b.inner(area); - b.render(area, buf); - inner_area - } - None => area, - }; + fn render( + mut self, + area: Rect, + buf: &mut Buffer, + state: &mut Self::State, + ) { + buf.set_style(area, self.style); + let text_area = match self.block.take() { + Some(b) => { + let inner_area = b.inner(area); + b.render(area, buf); + inner_area + } + None => area, + }; - if text_area.height < 1 { - return; - } + if text_area.height < 1 { + return; + } - let style = self.style; - let mut styled = self.text.lines.iter().flat_map(|spans| { - spans - .0 - .iter() - .flat_map(|span| span.styled_graphemes(style)) - // Required given the way composers work but might be refactored out if we change - // composers to operate on lines instead of a stream of graphemes. - .chain(iter::once(StyledGrapheme { - symbol: "\n", - style: self.style, - })) - }); + let style = self.style; + let mut styled = self.text.lines.iter().flat_map(|spans| { + spans + .0 + .iter() + .flat_map(|span| span.styled_graphemes(style)) + // Required given the way composers work but might be refactored out if we change + // composers to operate on lines instead of a stream of graphemes. + .chain(iter::once(StyledGrapheme { + symbol: "\n", + style: self.style, + })) + }); - let mut line_composer: Box = - if let Some(Wrap { trim }) = self.wrap { - Box::new(WordWrapper::new( - &mut styled, - text_area.width, - trim, - )) - } else { - let mut line_composer = Box::new(LineTruncator::new( - &mut styled, - text_area.width, - )); - if let Alignment::Left = self.alignment { - line_composer - .set_horizontal_offset(state.scroll.x); - } - line_composer - }; - let mut y = 0; - let mut end_reached = false; - while let Some((current_line, current_line_width)) = - line_composer.next_line() - { - if !end_reached && y >= state.scroll.y { - let mut x = get_line_offset( - current_line_width, - text_area.width, - self.alignment, - ); - for StyledGrapheme { symbol, style } in current_line { - buf.get_mut( - text_area.left() + x, - text_area.top() + y - state.scroll.y, - ) - .set_symbol(if symbol.is_empty() { - // If the symbol is empty, the last char which rendered last time will - // leave on the line. It's a quick fix. - " " - } else { - symbol - }) - .set_style(*style); - x += Cast::::cast(symbol.width()); - } - } - y += 1; - if y >= text_area.height + state.scroll.y { - end_reached = true; - } - } + let mut line_composer: Box = + if let Some(Wrap { trim }) = self.wrap { + Box::new(WordWrapper::new( + &mut styled, + text_area.width, + trim, + )) + } else { + let mut line_composer = Box::new(LineTruncator::new( + &mut styled, + text_area.width, + )); + if let Alignment::Left = self.alignment { + line_composer + .set_horizontal_offset(state.scroll.x); + } + line_composer + }; + let mut y = 0; + let mut end_reached = false; + while let Some((current_line, current_line_width)) = + line_composer.next_line() + { + if !end_reached && y >= state.scroll.y { + let mut x = get_line_offset( + current_line_width, + text_area.width, + self.alignment, + ); + for StyledGrapheme { symbol, style } in current_line { + buf.get_mut( + text_area.left() + x, + text_area.top() + y - state.scroll.y, + ) + .set_symbol(if symbol.is_empty() { + // If the symbol is empty, the last char which rendered last time will + // leave on the line. It's a quick fix. + " " + } else { + symbol + }) + .set_style(*style); + x += Cast::::cast(symbol.width()); + } + } + y += 1; + if y >= text_area.height + state.scroll.y { + end_reached = true; + } + } - state.lines = y; - state.height = area.height; - } + state.lines = y; + state.height = area.height; + } } diff --git a/src/ui/style.rs b/src/ui/style.rs index 747fd176..772cba05 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -4,15 +4,15 @@ use anyhow::Result; use asyncgit::{DiffLineType, StatusItemType}; use ron::{ - de::from_bytes, - ser::{to_string_pretty, PrettyConfig}, + de::from_bytes, + ser::{to_string_pretty, PrettyConfig}, }; use serde::{Deserialize, Serialize}; use std::{ - fs::{self, File}, - io::{Read, Write}, - path::PathBuf, - rc::Rc, + fs::{self, File}, + io::{Read, Write}, + path::PathBuf, + rc::Rc, }; use tui::style::{Color, Modifier, Style}; @@ -20,309 +20,309 @@ pub type SharedTheme = Rc; #[derive(Serialize, Deserialize, Debug)] pub struct Theme { - selected_tab: Color, - #[serde(with = "Color")] - command_fg: Color, - #[serde(with = "Color")] - selection_bg: Color, - #[serde(with = "Color")] - cmdbar_extra_lines_bg: Color, - #[serde(with = "Color")] - disabled_fg: Color, - #[serde(with = "Color")] - diff_line_add: Color, - #[serde(with = "Color")] - diff_line_delete: Color, - #[serde(with = "Color")] - diff_file_added: Color, - #[serde(with = "Color")] - diff_file_removed: Color, - #[serde(with = "Color")] - diff_file_moved: Color, - #[serde(with = "Color")] - diff_file_modified: Color, - #[serde(with = "Color")] - commit_hash: Color, - #[serde(with = "Color")] - commit_time: Color, - #[serde(with = "Color")] - commit_author: Color, - #[serde(with = "Color")] - danger_fg: Color, - #[serde(with = "Color")] - push_gauge_bg: Color, - #[serde(with = "Color")] - push_gauge_fg: Color, + selected_tab: Color, + #[serde(with = "Color")] + command_fg: Color, + #[serde(with = "Color")] + selection_bg: Color, + #[serde(with = "Color")] + cmdbar_extra_lines_bg: Color, + #[serde(with = "Color")] + disabled_fg: Color, + #[serde(with = "Color")] + diff_line_add: Color, + #[serde(with = "Color")] + diff_line_delete: Color, + #[serde(with = "Color")] + diff_file_added: Color, + #[serde(with = "Color")] + diff_file_removed: Color, + #[serde(with = "Color")] + diff_file_moved: Color, + #[serde(with = "Color")] + diff_file_modified: Color, + #[serde(with = "Color")] + commit_hash: Color, + #[serde(with = "Color")] + commit_time: Color, + #[serde(with = "Color")] + commit_author: Color, + #[serde(with = "Color")] + danger_fg: Color, + #[serde(with = "Color")] + push_gauge_bg: Color, + #[serde(with = "Color")] + push_gauge_fg: Color, } impl Theme { - pub fn scroll_bar_pos(&self) -> Style { - Style::default().fg(self.selection_bg) - } + pub fn scroll_bar_pos(&self) -> Style { + Style::default().fg(self.selection_bg) + } - pub fn block(&self, focus: bool) -> Style { - if focus { - Style::default() - } else { - Style::default().fg(self.disabled_fg) - } - } + pub fn block(&self, focus: bool) -> Style { + if focus { + Style::default() + } else { + Style::default().fg(self.disabled_fg) + } + } - pub fn title(&self, focused: bool) -> Style { - if focused { - Style::default().add_modifier(Modifier::BOLD) - } else { - Style::default().fg(self.disabled_fg) - } - } + pub fn title(&self, focused: bool) -> Style { + if focused { + Style::default().add_modifier(Modifier::BOLD) + } else { + Style::default().fg(self.disabled_fg) + } + } - pub fn branch(&self, selected: bool, head: bool) -> Style { - let branch = if head { - Style::default().add_modifier(Modifier::BOLD) - } else { - Style::default() - }; + pub fn branch(&self, selected: bool, head: bool) -> Style { + let branch = if head { + Style::default().add_modifier(Modifier::BOLD) + } else { + Style::default() + }; - if selected { - branch.patch(Style::default().bg(self.selection_bg)) - } else { - branch - } - } + if selected { + branch.patch(Style::default().bg(self.selection_bg)) + } else { + branch + } + } - pub fn tab(&self, selected: bool) -> Style { - if selected { - self.text(true, false) - .fg(self.selected_tab) - .add_modifier(Modifier::UNDERLINED) - } else { - self.text(false, false) - } - } + pub fn tab(&self, selected: bool) -> Style { + if selected { + self.text(true, false) + .fg(self.selected_tab) + .add_modifier(Modifier::UNDERLINED) + } else { + self.text(false, false) + } + } - pub fn tags(&self, selected: bool) -> Style { - Style::default() - .fg(self.selected_tab) - .add_modifier(Modifier::BOLD) - .bg(if selected { - self.selection_bg - } else { - Color::Reset - }) - } + pub fn tags(&self, selected: bool) -> Style { + Style::default() + .fg(self.selected_tab) + .add_modifier(Modifier::BOLD) + .bg(if selected { + self.selection_bg + } else { + Color::Reset + }) + } - pub fn text(&self, enabled: bool, selected: bool) -> Style { - match (enabled, selected) { - (false, _) => Style::default().fg(self.disabled_fg), - (true, false) => Style::default(), - (true, true) => Style::default() - .fg(self.command_fg) - .bg(self.selection_bg), - } - } + pub fn text(&self, enabled: bool, selected: bool) -> Style { + match (enabled, selected) { + (false, _) => Style::default().fg(self.disabled_fg), + (true, false) => Style::default(), + (true, true) => Style::default() + .fg(self.command_fg) + .bg(self.selection_bg), + } + } - pub fn item(&self, typ: StatusItemType, selected: bool) -> Style { - let style = match typ { - StatusItemType::New => { - Style::default().fg(self.diff_file_added) - } - StatusItemType::Modified => { - Style::default().fg(self.diff_file_modified) - } - StatusItemType::Deleted => { - Style::default().fg(self.diff_file_removed) - } - StatusItemType::Renamed => { - Style::default().fg(self.diff_file_moved) - } - StatusItemType::Conflicted => Style::default() - .fg(self.diff_file_modified) - .add_modifier(Modifier::BOLD), - StatusItemType::Typechange => Style::default(), - }; + pub fn item(&self, typ: StatusItemType, selected: bool) -> Style { + let style = match typ { + StatusItemType::New => { + Style::default().fg(self.diff_file_added) + } + StatusItemType::Modified => { + Style::default().fg(self.diff_file_modified) + } + StatusItemType::Deleted => { + Style::default().fg(self.diff_file_removed) + } + StatusItemType::Renamed => { + Style::default().fg(self.diff_file_moved) + } + StatusItemType::Conflicted => Style::default() + .fg(self.diff_file_modified) + .add_modifier(Modifier::BOLD), + StatusItemType::Typechange => Style::default(), + }; - self.apply_select(style, selected) - } + self.apply_select(style, selected) + } - pub fn file_tree_item( - &self, - is_folder: bool, - selected: bool, - ) -> Style { - let style = if is_folder { - Style::default() - } else { - Style::default().fg(self.diff_file_modified) - }; + pub fn file_tree_item( + &self, + is_folder: bool, + selected: bool, + ) -> Style { + let style = if is_folder { + Style::default() + } else { + Style::default().fg(self.diff_file_modified) + }; - self.apply_select(style, selected) - } + self.apply_select(style, selected) + } - fn apply_select(&self, style: Style, selected: bool) -> Style { - if selected { - style.bg(self.selection_bg) - } else { - style - } - } + fn apply_select(&self, style: Style, selected: bool) -> Style { + if selected { + style.bg(self.selection_bg) + } else { + style + } + } - pub fn option(&self, on: bool) -> Style { - if on { - Style::default().fg(self.diff_line_add) - } else { - Style::default().fg(self.diff_line_delete) - } - } + pub fn option(&self, on: bool) -> Style { + if on { + Style::default().fg(self.diff_line_add) + } else { + Style::default().fg(self.diff_line_delete) + } + } - pub fn diff_hunk_marker(&self, selected: bool) -> Style { - if selected { - Style::default().bg(self.selection_bg) - } else { - Style::default().fg(self.disabled_fg) - } - } + pub fn diff_hunk_marker(&self, selected: bool) -> Style { + if selected { + Style::default().bg(self.selection_bg) + } else { + Style::default().fg(self.disabled_fg) + } + } - pub fn diff_line( - &self, - typ: DiffLineType, - selected: bool, - ) -> Style { - let style = match typ { - DiffLineType::Add => { - Style::default().fg(self.diff_line_add) - } - DiffLineType::Delete => { - Style::default().fg(self.diff_line_delete) - } - DiffLineType::Header => Style::default() - .fg(self.disabled_fg) - .add_modifier(Modifier::BOLD), - DiffLineType::None => Style::default().fg(if selected { - self.command_fg - } else { - Color::Reset - }), - }; + pub fn diff_line( + &self, + typ: DiffLineType, + selected: bool, + ) -> Style { + let style = match typ { + DiffLineType::Add => { + Style::default().fg(self.diff_line_add) + } + DiffLineType::Delete => { + Style::default().fg(self.diff_line_delete) + } + DiffLineType::Header => Style::default() + .fg(self.disabled_fg) + .add_modifier(Modifier::BOLD), + DiffLineType::None => Style::default().fg(if selected { + self.command_fg + } else { + Color::Reset + }), + }; - self.apply_select(style, selected) - } + self.apply_select(style, selected) + } - pub fn text_danger(&self) -> Style { - Style::default().fg(self.danger_fg) - } + pub fn text_danger(&self) -> Style { + Style::default().fg(self.danger_fg) + } - pub fn commandbar(&self, enabled: bool, line: usize) -> Style { - if enabled { - Style::default().fg(self.command_fg) - } else { - Style::default().fg(self.disabled_fg) - } - .bg(if line == 0 { - self.selection_bg - } else { - self.cmdbar_extra_lines_bg - }) - } + pub fn commandbar(&self, enabled: bool, line: usize) -> Style { + if enabled { + Style::default().fg(self.command_fg) + } else { + Style::default().fg(self.disabled_fg) + } + .bg(if line == 0 { + self.selection_bg + } else { + self.cmdbar_extra_lines_bg + }) + } - pub fn commit_hash(&self, selected: bool) -> Style { - self.apply_select( - Style::default().fg(self.commit_hash), - selected, - ) - } - pub fn commit_time(&self, selected: bool) -> Style { - self.apply_select( - Style::default().fg(self.commit_time), - selected, - ) - } - pub fn commit_author(&self, selected: bool) -> Style { - self.apply_select( - Style::default().fg(self.commit_author), - selected, - ) - } + pub fn commit_hash(&self, selected: bool) -> Style { + self.apply_select( + Style::default().fg(self.commit_hash), + selected, + ) + } + pub fn commit_time(&self, selected: bool) -> Style { + self.apply_select( + Style::default().fg(self.commit_time), + selected, + ) + } + pub fn commit_author(&self, selected: bool) -> Style { + self.apply_select( + Style::default().fg(self.commit_author), + selected, + ) + } - pub fn commit_hash_in_blame( - &self, - is_blamed_commit: bool, - ) -> Style { - if is_blamed_commit { - Style::default() - .fg(self.commit_hash) - .add_modifier(Modifier::BOLD) - } else { - Style::default().fg(self.commit_hash) - } - } + pub fn commit_hash_in_blame( + &self, + is_blamed_commit: bool, + ) -> Style { + if is_blamed_commit { + Style::default() + .fg(self.commit_hash) + .add_modifier(Modifier::BOLD) + } else { + Style::default().fg(self.commit_hash) + } + } - pub fn push_gauge(&self) -> Style { - Style::default() - .fg(self.push_gauge_fg) - .bg(self.push_gauge_bg) - } + pub fn push_gauge(&self) -> Style { + Style::default() + .fg(self.push_gauge_fg) + .bg(self.push_gauge_bg) + } - // This will only be called when theme.ron doesn't already exists - fn save(&self, theme_file: PathBuf) -> Result<()> { - let mut file = File::create(theme_file)?; - let data = to_string_pretty(self, PrettyConfig::default())?; - file.write_all(data.as_bytes())?; - Ok(()) - } + // This will only be called when theme.ron doesn't already exists + fn save(&self, theme_file: PathBuf) -> Result<()> { + let mut file = File::create(theme_file)?; + let data = to_string_pretty(self, PrettyConfig::default())?; + file.write_all(data.as_bytes())?; + Ok(()) + } - fn read_file(theme_file: PathBuf) -> Result { - let mut f = File::open(theme_file)?; - let mut buffer = Vec::new(); - f.read_to_end(&mut buffer)?; - Ok(from_bytes(&buffer)?) - } + fn read_file(theme_file: PathBuf) -> Result { + let mut f = File::open(theme_file)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + Ok(from_bytes(&buffer)?) + } - pub fn init(file: PathBuf) -> Result { - if file.exists() { - match Self::read_file(file.clone()) { - Err(e) => { - let config_path = file.clone(); - let config_path_old = - format!("{}.old", file.to_string_lossy()); - fs::rename( - config_path.clone(), - config_path_old.clone(), - )?; + pub fn init(file: PathBuf) -> Result { + if file.exists() { + match Self::read_file(file.clone()) { + Err(e) => { + let config_path = file.clone(); + let config_path_old = + format!("{}.old", file.to_string_lossy()); + fs::rename( + config_path.clone(), + config_path_old.clone(), + )?; - Self::default().save(file)?; + Self::default().save(file)?; - Err(anyhow::anyhow!("{}\n Old file was renamed to {:?}.\n Defaults loaded and saved as {:?}", + Err(anyhow::anyhow!("{}\n Old file was renamed to {:?}.\n Defaults loaded and saved as {:?}", e,config_path_old,config_path.to_string_lossy())) - } - Ok(res) => Ok(res), - } - } else { - Self::default().save(file)?; - Ok(Self::default()) - } - } + } + Ok(res) => Ok(res), + } + } else { + Self::default().save(file)?; + Ok(Self::default()) + } + } } impl Default for Theme { - fn default() -> Self { - Self { - selected_tab: Color::Reset, - command_fg: Color::White, - selection_bg: Color::Blue, - cmdbar_extra_lines_bg: Color::Blue, - disabled_fg: Color::DarkGray, - diff_line_add: Color::Green, - diff_line_delete: Color::Red, - diff_file_added: Color::LightGreen, - diff_file_removed: Color::LightRed, - diff_file_moved: Color::LightMagenta, - diff_file_modified: Color::Yellow, - commit_hash: Color::Magenta, - commit_time: Color::LightCyan, - commit_author: Color::Green, - danger_fg: Color::Red, - push_gauge_bg: Color::Blue, - push_gauge_fg: Color::Reset, - } - } + fn default() -> Self { + Self { + selected_tab: Color::Reset, + command_fg: Color::White, + selection_bg: Color::Blue, + cmdbar_extra_lines_bg: Color::Blue, + disabled_fg: Color::DarkGray, + diff_line_add: Color::Green, + diff_line_delete: Color::Red, + diff_file_added: Color::LightGreen, + diff_file_removed: Color::LightRed, + diff_file_moved: Color::LightMagenta, + diff_file_modified: Color::Yellow, + commit_hash: Color::Magenta, + commit_time: Color::LightCyan, + commit_author: Color::Green, + danger_fg: Color::Red, + push_gauge_bg: Color::Blue, + push_gauge_fg: Color::Reset, + } + } } diff --git a/src/ui/syntax_text.rs b/src/ui/syntax_text.rs index 0cc4bdb7..3dcad68c 100644 --- a/src/ui/syntax_text.rs +++ b/src/ui/syntax_text.rs @@ -2,189 +2,189 @@ use asyncgit::asyncjob::AsyncJob; use lazy_static::lazy_static; use scopetime::scope_time; use std::{ - ffi::OsStr, - ops::Range, - path::{Path, PathBuf}, - sync::{Arc, Mutex}, + ffi::OsStr, + ops::Range, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, }; use syntect::{ - highlighting::{ - FontStyle, HighlightState, Highlighter, - RangedHighlightIterator, Style, ThemeSet, - }, - parsing::{ParseState, ScopeStack, SyntaxSet}, + highlighting::{ + FontStyle, HighlightState, Highlighter, + RangedHighlightIterator, Style, ThemeSet, + }, + parsing::{ParseState, ScopeStack, SyntaxSet}, }; use tui::text::{Span, Spans}; struct SyntaxLine { - items: Vec<(Style, usize, Range)>, + items: Vec<(Style, usize, Range)>, } pub struct SyntaxText { - text: String, - lines: Vec, - path: PathBuf, + text: String, + lines: Vec, + path: PathBuf, } lazy_static! { - static ref SYNTAX_SET: SyntaxSet = - SyntaxSet::load_defaults_nonewlines(); - static ref THEME_SET: ThemeSet = ThemeSet::load_defaults(); + static ref SYNTAX_SET: SyntaxSet = + SyntaxSet::load_defaults_nonewlines(); + static ref THEME_SET: ThemeSet = ThemeSet::load_defaults(); } impl SyntaxText { - pub fn new(text: String, file_path: &Path) -> Self { - scope_time!("syntax_highlighting"); - log::debug!("syntax: {:?}", file_path); + pub fn new(text: String, file_path: &Path) -> Self { + scope_time!("syntax_highlighting"); + log::debug!("syntax: {:?}", file_path); - let mut state = { - let syntax = file_path - .extension() - .and_then(OsStr::to_str) - .map_or_else( - || { - SYNTAX_SET.find_syntax_by_path( - file_path.to_str().unwrap_or_default(), - ) - }, - |ext| SYNTAX_SET.find_syntax_by_extension(ext), - ); + let mut state = { + let syntax = file_path + .extension() + .and_then(OsStr::to_str) + .map_or_else( + || { + SYNTAX_SET.find_syntax_by_path( + file_path.to_str().unwrap_or_default(), + ) + }, + |ext| SYNTAX_SET.find_syntax_by_extension(ext), + ); - ParseState::new(syntax.unwrap_or_else(|| { - SYNTAX_SET.find_syntax_plain_text() - })) - }; + ParseState::new(syntax.unwrap_or_else(|| { + SYNTAX_SET.find_syntax_plain_text() + })) + }; - let highlighter = Highlighter::new( - &THEME_SET.themes["base16-eighties.dark"], - ); + let highlighter = Highlighter::new( + &THEME_SET.themes["base16-eighties.dark"], + ); - let mut syntax_lines: Vec = Vec::new(); + let mut syntax_lines: Vec = Vec::new(); - let mut highlight_state = - HighlightState::new(&highlighter, ScopeStack::new()); + let mut highlight_state = + HighlightState::new(&highlighter, ScopeStack::new()); - for (number, line) in text.lines().enumerate() { - let ops = state.parse_line(line, &SYNTAX_SET); - let iter = RangedHighlightIterator::new( - &mut highlight_state, - &ops[..], - line, - &highlighter, - ); + for (number, line) in text.lines().enumerate() { + let ops = state.parse_line(line, &SYNTAX_SET); + let iter = RangedHighlightIterator::new( + &mut highlight_state, + &ops[..], + line, + &highlighter, + ); - syntax_lines.push(SyntaxLine { - items: iter - .map(|(style, _, range)| (style, number, range)) - .collect(), - }); - } + syntax_lines.push(SyntaxLine { + items: iter + .map(|(style, _, range)| (style, number, range)) + .collect(), + }); + } - Self { - text, - lines: syntax_lines, - path: file_path.into(), - } - } + Self { + text, + lines: syntax_lines, + path: file_path.into(), + } + } - /// - pub fn path(&self) -> &Path { - &self.path - } + /// + pub fn path(&self) -> &Path { + &self.path + } } impl<'a> From<&'a SyntaxText> for tui::text::Text<'a> { - fn from(v: &'a SyntaxText) -> Self { - let mut result_lines: Vec = - Vec::with_capacity(v.lines.len()); + fn from(v: &'a SyntaxText) -> Self { + let mut result_lines: Vec = + Vec::with_capacity(v.lines.len()); - for (syntax_line, line_content) in - v.lines.iter().zip(v.text.lines()) - { - let mut line_span = - Spans(Vec::with_capacity(syntax_line.items.len())); + for (syntax_line, line_content) in + v.lines.iter().zip(v.text.lines()) + { + let mut line_span = + Spans(Vec::with_capacity(syntax_line.items.len())); - for (style, _, range) in &syntax_line.items { - let item_content = &line_content[range.clone()]; - let item_style = syntact_style_to_tui(style); + for (style, _, range) in &syntax_line.items { + let item_content = &line_content[range.clone()]; + let item_style = syntact_style_to_tui(style); - line_span - .0 - .push(Span::styled(item_content, item_style)); - } + line_span + .0 + .push(Span::styled(item_content, item_style)); + } - result_lines.push(line_span); - } + result_lines.push(line_span); + } - result_lines.into() - } + result_lines.into() + } } fn syntact_style_to_tui(style: &Style) -> tui::style::Style { - let mut res = - tui::style::Style::default().fg(tui::style::Color::Rgb( - style.foreground.r, - style.foreground.g, - style.foreground.b, - )); + let mut res = + tui::style::Style::default().fg(tui::style::Color::Rgb( + style.foreground.r, + style.foreground.g, + style.foreground.b, + )); - if style.font_style.contains(FontStyle::BOLD) { - res = res.add_modifier(tui::style::Modifier::BOLD); - } - if style.font_style.contains(FontStyle::ITALIC) { - res = res.add_modifier(tui::style::Modifier::ITALIC); - } - if style.font_style.contains(FontStyle::UNDERLINE) { - res = res.add_modifier(tui::style::Modifier::UNDERLINED); - } + if style.font_style.contains(FontStyle::BOLD) { + res = res.add_modifier(tui::style::Modifier::BOLD); + } + if style.font_style.contains(FontStyle::ITALIC) { + res = res.add_modifier(tui::style::Modifier::ITALIC); + } + if style.font_style.contains(FontStyle::UNDERLINE) { + res = res.add_modifier(tui::style::Modifier::UNDERLINED); + } - res + res } enum JobState { - Request((String, String)), - Response(SyntaxText), + Request((String, String)), + Response(SyntaxText), } #[derive(Clone, Default)] pub struct AsyncSyntaxJob { - state: Arc>>, + state: Arc>>, } impl AsyncSyntaxJob { - pub fn new(content: String, path: String) -> Self { - Self { - state: Arc::new(Mutex::new(Some(JobState::Request(( - content, path, - ))))), - } - } + pub fn new(content: String, path: String) -> Self { + Self { + state: Arc::new(Mutex::new(Some(JobState::Request(( + content, path, + ))))), + } + } - pub fn result(&self) -> Option { - if let Ok(mut state) = self.state.lock() { - if let Some(state) = state.take() { - return match state { - JobState::Request(_) => None, - JobState::Response(text) => Some(text), - }; - } - } + pub fn result(&self) -> Option { + if let Ok(mut state) = self.state.lock() { + if let Some(state) = state.take() { + return match state { + JobState::Request(_) => None, + JobState::Response(text) => Some(text), + }; + } + } - None - } + None + } } impl AsyncJob for AsyncSyntaxJob { - fn run(&mut self) { - if let Ok(mut state) = self.state.lock() { - *state = state.take().map(|state| match state { - JobState::Request((content, path)) => { - let syntax = - SyntaxText::new(content, Path::new(&path)); - JobState::Response(syntax) - } - JobState::Response(res) => JobState::Response(res), - }); - } - } + fn run(&mut self) { + if let Ok(mut state) = self.state.lock() { + *state = state.take().map(|state| match state { + JobState::Request((content, path)) => { + let syntax = + SyntaxText::new(content, Path::new(&path)); + JobState::Response(syntax) + } + JobState::Response(res) => JobState::Response(res), + }); + } + } } diff --git a/src/version.rs b/src/version.rs index 64d1aa2b..02af65c2 100644 --- a/src/version.rs +++ b/src/version.rs @@ -3,33 +3,33 @@ use std::{env, fmt}; /// version type #[derive(Default)] pub struct Version { - major: u32, - minor: u32, - patch: u32, + major: u32, + minor: u32, + patch: u32, } impl Version { - /// read version at compile time from env variables - pub fn new() -> Self { - let mut res = Self::default(); - let major_str = env!("CARGO_PKG_VERSION_MAJOR"); - if let Ok(major) = major_str.parse::() { - res.major = major; - } - let minor_str = env!("CARGO_PKG_VERSION_MINOR"); - if let Ok(minor) = minor_str.parse::() { - res.minor = minor; - } - let patch_str = env!("CARGO_PKG_VERSION_PATCH"); - if let Ok(patch) = patch_str.parse::() { - res.patch = patch; - } - res - } + /// read version at compile time from env variables + pub fn new() -> Self { + let mut res = Self::default(); + let major_str = env!("CARGO_PKG_VERSION_MAJOR"); + if let Ok(major) = major_str.parse::() { + res.major = major; + } + let minor_str = env!("CARGO_PKG_VERSION_MINOR"); + if let Ok(minor) = minor_str.parse::() { + res.minor = minor; + } + let patch_str = env!("CARGO_PKG_VERSION_PATCH"); + if let Ok(patch) = patch_str.parse::() { + res.patch = patch; + } + res + } } impl fmt::Display for Version { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "v{}.{}.{}", self.major, self.minor, self.patch) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "v{}.{}.{}", self.major, self.minor, self.patch) + } }