Skip to content

Commit

Permalink
add command and keybding to jump to next/prev hunk
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalkuthe committed Nov 9, 2022
1 parent 8e02b6b commit 11cea91
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
34 changes: 34 additions & 0 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ impl MappableCommand {
goto_last_diag, "Goto last diagnostic",
goto_next_diag, "Goto next diagnostic",
goto_prev_diag, "Goto previous diagnostic",
goto_next_hunk, "Goto next hunk of changes",
goto_prev_hunk, "Goto previous hunk of changes",
goto_line_start, "Goto line start",
goto_line_end, "Goto line end",
goto_next_buffer, "Goto next buffer",
Expand Down Expand Up @@ -2895,6 +2897,38 @@ fn goto_prev_diag(cx: &mut Context) {
goto_pos(editor, pos);
}

fn goto_next_hunk(cx: &mut Context) {
goto_next_hunk_impl::<false>(cx)
}

fn goto_prev_hunk(cx: &mut Context) {
goto_next_hunk_impl::<true>(cx)
}

fn goto_next_hunk_impl<const REVERSE: bool>(cx: &mut Context) {
let editor = &mut cx.editor;
let (view, doc) = current!(editor);

let cursor_pos = doc
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));

let cursor_line = doc.text().char_to_line(cursor_pos);

let next_changed_line = doc.diff_handle().and_then(|diff_handle| {
let hunks = diff_handle.hunks();
let hunk_idx = hunks.next_hunk::<REVERSE>(cursor_line as u32)?;
let line = hunks.nth_hunk(hunk_idx).after.start;
Some(line)
});

if let Some(next_change_line) = next_changed_line {
let pos = doc.text().line_to_char(next_change_line as usize);
goto_pos(cx.editor, pos)
}
}

pub mod insert {
use super::*;
pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
Expand Down
2 changes: 2 additions & 0 deletions helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"[" => { "Left bracket"
"d" => goto_prev_diag,
"D" => goto_first_diag,
"g" => goto_prev_hunk,
"f" => goto_prev_function,
"c" => goto_prev_class,
"a" => goto_prev_parameter,
Expand All @@ -111,6 +112,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"]" => { "Right bracket"
"d" => goto_next_diag,
"D" => goto_last_diag,
"g" => goto_next_hunk,
"f" => goto_next_function,
"c" => goto_next_class,
"a" => goto_next_parameter,
Expand Down
56 changes: 56 additions & 0 deletions helix-vcs/src/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,60 @@ impl FileHunks<'_> {
None => Hunk::NONE,
}
}

fn next_hunk_impl<const INVERT: bool, const REVERSE: bool>(&self, line: u32) -> Option<u32> {
let hunk_range = |hunk: &Hunk| {
if INVERT {
hunk.before.clone()
} else {
hunk.after.clone()
}
};
let res = self.hunks.binary_search_by_key(&line, |hunk| {
let range = hunk_range(hunk);
if REVERSE {
range.end
} else {
range.start
}
});

if REVERSE {
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, howver a specical 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 previus 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),
}
} else {
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 next_hunk<const REVERSE: bool>(&self, line: u32) -> Option<u32> {
if self.inverted {
self.next_hunk_impl::<true, REVERSE>(line)
} else {
self.next_hunk_impl::<false, REVERSE>(line)
}
}
}

0 comments on commit 11cea91

Please sign in to comment.