aboutsummaryrefslogtreecommitdiff
path: root/helix-vcs
diff options
context:
space:
mode:
authorPascal Kuthe2022-12-06 14:18:33 +0000
committerGitHub2022-12-06 14:18:33 +0000
commitaf532147c97987d6170dc06a52aa3434ebf1b9d4 (patch)
treeff1dc27e976a7ca8ac0be4a8eaebb51ac12c4f69 /helix-vcs
parent453a75a3739338348024b6c676231aef9ef6cb7b (diff)
Add command/keybinding to jump between hunks (#4650)
* add command and keybding to jump to next/prev hunk * add textobject for change * Update helix-vcs/src/diff.rs Co-authored-by: Michael Davis <mcarsondavis@gmail.com> * select entire hunk instead of first char * fix selection range Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Diffstat (limited to 'helix-vcs')
-rw-r--r--helix-vcs/src/diff.rs81
1 files changed, 81 insertions, 0 deletions
diff --git a/helix-vcs/src/diff.rs b/helix-vcs/src/diff.rs
index b1acd1f2..9c6a362f 100644
--- a/helix-vcs/src/diff.rs
+++ b/helix-vcs/src/diff.rs
@@ -195,4 +195,85 @@ impl FileHunks<'_> {
pub fn is_empty(&self) -> bool {
self.len() == 0
}
+
+ pub fn next_hunk(&self, line: u32) -> Option<u32> {
+ let hunk_range = if self.inverted {
+ |hunk: &Hunk| hunk.before.clone()
+ } else {
+ |hunk: &Hunk| hunk.after.clone()
+ };
+
+ let res = self
+ .hunks
+ .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
+
+ match res {
+ // Search found a hunk that starts exactly at this line, return the next hunk if it exists.
+ Ok(pos) if pos + 1 == self.hunks.len() => None,
+ Ok(pos) => Some(pos as u32 + 1),
+
+ // No hunk starts exactly at this line, so the search returns
+ // the position where a hunk starting at this line should be inserted.
+ // That position is exactly the position of the next hunk or the end
+ // of the list if no such hunk exists
+ Err(pos) if pos == self.hunks.len() => None,
+ Err(pos) => Some(pos as u32),
+ }
+ }
+
+ pub fn prev_hunk(&self, line: u32) -> Option<u32> {
+ let hunk_range = if self.inverted {
+ |hunk: &Hunk| hunk.before.clone()
+ } else {
+ |hunk: &Hunk| hunk.after.clone()
+ };
+ let res = self
+ .hunks
+ .binary_search_by_key(&line, |hunk| hunk_range(hunk).end);
+
+ match res {
+ // Search found a hunk that ends exactly at this line (so it does not include the current line).
+ // We can usually just return that hunk, however a special case for empty hunk is necessary
+ // which represents a pure removal.
+ // Removals are technically empty but are still shown as single line hunks
+ // and as such we must jump to the previous hunk (if it exists) if we are already inside the removal
+ Ok(pos) if !hunk_range(&self.hunks[pos]).is_empty() => Some(pos as u32),
+
+ // No hunk ends exactly at this line, so the search returns
+ // the position where a hunk ending at this line should be inserted.
+ // That position before this one is exactly the position of the previous hunk
+ Err(0) | Ok(0) => None,
+ Err(pos) | Ok(pos) => Some(pos as u32 - 1),
+ }
+ }
+
+ pub fn hunk_at(&self, line: u32, include_removal: bool) -> Option<u32> {
+ let hunk_range = if self.inverted {
+ |hunk: &Hunk| hunk.before.clone()
+ } else {
+ |hunk: &Hunk| hunk.after.clone()
+ };
+
+ let res = self
+ .hunks
+ .binary_search_by_key(&line, |hunk| hunk_range(hunk).start);
+
+ match res {
+ // Search found a hunk that starts exactly at this line, return it
+ Ok(pos) => Some(pos as u32),
+
+ // No hunk starts exactly at this line, so the search returns
+ // the position where a hunk starting at this line should be inserted.
+ // The previous hunk contains this hunk if it exists and doesn't end before this line
+ Err(0) => None,
+ Err(pos) => {
+ let hunk = hunk_range(&self.hunks[pos - 1]);
+ if hunk.end > line || include_removal && hunk.start == line && hunk.is_empty() {
+ Some(pos as u32 - 1)
+ } else {
+ None
+ }
+ }
+ }
+ }
}