diff --git a/CHANGELOG.md b/CHANGELOG.md index 5feaf039..ba522f4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * file blame at right revision from commit-details [[@heiskane](https://github.com/heiskane)] ([#1122](https://github.com/extrawurst/gitui/issues/1122)) * add `regex-fancy` and `regex-onig` features to allow building Syntect with Onigumara regex engine instead of the default engine based on fancy-regex [[@jirutka](https://github.com/jirutka)] * add `vendor-openssl` feature to allow building without vendored openssl [[@jirutka](https://github.com/jirutka)] +* allow copying marked commits [[@remique](https://github.com/remique)] ([#1288](https://github.com/extrawurst/gitui/issues/1288)) ### Fixes * remove insecure dependency `ansi_term` ([#1290](https://github.com/extrawurst/gitui/issues/1290)) diff --git a/src/components/commitlist.rs b/src/components/commitlist.rs index 4e0aa3f9..ebe4edc4 100644 --- a/src/components/commitlist.rs +++ b/src/components/commitlist.rs @@ -34,7 +34,7 @@ pub struct CommitList { branch: Option, count_total: usize, items: ItemBatch, - marked: Vec, + marked: Vec<(usize, CommitId)>, scroll_state: (Instant, f32), tags: Option, current_size: Cell<(u16, u16)>, @@ -134,7 +134,7 @@ impl CommitList { } /// - pub fn marked(&self) -> &[CommitId] { + pub fn marked(&self) -> &[(usize, CommitId)] { &self.marked } @@ -143,11 +143,85 @@ impl CommitList { self.marked.clear(); } + /// + pub fn marked_indexes(&self) -> Vec { + let (indexes, _): (Vec, Vec<_>) = + self.marked.iter().copied().unzip(); + + indexes + } + + /// + pub fn marked_commits(&self) -> Vec { + let (_, commits): (Vec<_>, Vec) = + self.marked.iter().copied().unzip(); + + commits + } + + fn marked_consecutive(&self) -> bool { + let marked = self.marked_indexes(); + + for i in 1..marked.len() { + if marked[i - 1] + 1 != marked[i] { + return false; + } + } + + true + } + + pub fn copy_marked_hashes(&self) -> Result<()> { + if self.marked_consecutive() { + let m = self.marked_indexes(); + + let first = self.items.iter().nth(m[0]); + + let last = self.items.iter().nth(m[m.len() - 1]); + + if let (Some(f), Some(l)) = (first, last) { + let yank = + format!("{}^..{}", f.hash_short, l.hash_short); + crate::clipboard::copy_string(&yank)?; + }; + } else { + let separate = self + .marked_indexes() + .iter() + .map(|e| { + self.items + .iter() + .nth(*e) + .map_or_else(String::new, |le| { + le.hash_short.to_string() + }) + }) + .join(" "); + + crate::clipboard::copy_string(&separate)?; + } + + 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)?; + match self.marked_count() { + 0 => { + if let Some(e) = self.items.iter().nth( + self.selection + .saturating_sub(self.items.index_offset()), + ) { + crate::clipboard::copy_string(&e.hash_short)?; + } + } + 1 => { + if let Some(e) = + self.items.iter().nth(self.marked_indexes()[0]) + { + crate::clipboard::copy_string(&e.hash_short)?; + } + } + _ => {} } Ok(()) } @@ -191,10 +265,17 @@ impl CommitList { fn mark(&mut self) { if let Some(e) = self.selected_entry() { let id = e.id; + let selected = self + .selection + .saturating_sub(self.items.index_offset()); if self.is_marked(&id).unwrap_or_default() { - self.marked.retain(|marked| marked != &id); + self.marked.retain(|marked| marked.1 != id); } else { - self.marked.push(id); + self.marked.push((selected, id)); + + self.marked.sort_unstable_by(|first, second| { + first.0.cmp(&second.0) + }); } } } @@ -227,7 +308,8 @@ impl CommitList { if self.marked.is_empty() { None } else { - let found = self.marked.iter().any(|entry| entry == id); + let found = + self.marked.iter().any(|entry| entry.1 == *id); Some(found) } } diff --git a/src/tabs/revlog.rs b/src/tabs/revlog.rs index 9437fc00..710617ca 100644 --- a/src/tabs/revlog.rs +++ b/src/tabs/revlog.rs @@ -165,7 +165,11 @@ impl Revlog { } fn copy_commit_hash(&self) -> Result<()> { - self.list.copy_entry_hash()?; + if self.list.marked_count() > 1 { + self.list.copy_marked_hashes()?; + } else { + self.list.copy_entry_hash()?; + } Ok(()) } @@ -328,7 +332,7 @@ impl Component for Revlog { self.queue.push(InternalEvent::OpenPopup( StackablePopupOpen::CompareCommits( InspectCommitOpen::new( - self.list.marked()[0], + self.list.marked()[0].1, ), ), )); @@ -339,8 +343,8 @@ impl Component for Revlog { self.queue.push(InternalEvent::OpenPopup( StackablePopupOpen::CompareCommits( InspectCommitOpen { - commit_id: marked[0], - compare_id: Some(marked[1]), + commit_id: marked[0].1, + compare_id: Some(marked[1].1), tags: None, }, ), diff --git a/src/tabs/stashlist.rs b/src/tabs/stashlist.rs index dc3bc981..1d61d988 100644 --- a/src/tabs/stashlist.rs +++ b/src/tabs/stashlist.rs @@ -78,7 +78,7 @@ impl StashList { fn drop_stash(&mut self) { if self.list.marked_count() > 0 { self.queue.push(InternalEvent::ConfirmAction( - Action::StashDrop(self.list.marked().to_vec()), + Action::StashDrop(self.list.marked_commits()), )); } else if let Some(e) = self.list.selected_entry() { self.queue.push(InternalEvent::ConfirmAction(