summaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/src/commands.rs121
-rw-r--r--helix-term/src/keymap/default.rs4
2 files changed, 125 insertions, 0 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 26389026..1843e7a2 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3,6 +3,7 @@ pub(crate) mod lsp;
pub(crate) mod typed;
pub use dap::*;
+use helix_vcs::Hunk;
pub use lsp::*;
use tui::text::Spans;
pub use typed::*;
@@ -308,6 +309,10 @@ impl MappableCommand {
goto_last_diag, "Goto last diagnostic",
goto_next_diag, "Goto next diagnostic",
goto_prev_diag, "Goto previous diagnostic",
+ goto_next_change, "Goto next change",
+ goto_prev_change, "Goto previous change",
+ goto_first_change, "Goto first change",
+ goto_last_change, "Goto last change",
goto_line_start, "Goto line start",
goto_line_end, "Goto line end",
goto_next_buffer, "Goto next buffer",
@@ -2912,6 +2917,100 @@ fn goto_prev_diag(cx: &mut Context) {
goto_pos(editor, pos);
}
+fn goto_first_change(cx: &mut Context) {
+ goto_first_change_impl(cx, false);
+}
+
+fn goto_last_change(cx: &mut Context) {
+ goto_first_change_impl(cx, true);
+}
+
+fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
+ let editor = &mut cx.editor;
+ let (_, doc) = current!(editor);
+ if let Some(handle) = doc.diff_handle() {
+ let hunk = {
+ let hunks = handle.hunks();
+ let idx = if reverse {
+ hunks.len().saturating_sub(1)
+ } else {
+ 0
+ };
+ hunks.nth_hunk(idx)
+ };
+ if hunk != Hunk::NONE {
+ let pos = doc.text().line_to_char(hunk.after.start as usize);
+ goto_pos(editor, pos)
+ }
+ }
+}
+
+fn goto_next_change(cx: &mut Context) {
+ goto_next_change_impl(cx, Direction::Forward)
+}
+
+fn goto_prev_change(cx: &mut Context) {
+ goto_next_change_impl(cx, Direction::Backward)
+}
+
+fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
+ let count = cx.count() as u32 - 1;
+ let motion = move |editor: &mut Editor| {
+ let (view, doc) = current!(editor);
+ let doc_text = doc.text().slice(..);
+ let diff_handle = if let Some(diff_handle) = doc.diff_handle() {
+ diff_handle
+ } else {
+ editor.set_status("Diff is not available in current buffer");
+ return;
+ };
+
+ let selection = doc.selection(view.id).clone().transform(|range| {
+ let cursor_line = range.cursor_line(doc_text) as u32;
+
+ let hunks = diff_handle.hunks();
+ let hunk_idx = match direction {
+ Direction::Forward => hunks
+ .next_hunk(cursor_line)
+ .map(|idx| (idx + count).min(hunks.len() - 1)),
+ Direction::Backward => hunks
+ .prev_hunk(cursor_line)
+ .map(|idx| idx.saturating_sub(count)),
+ };
+ // TODO refactor with let..else once MSRV reaches 1.65
+ let hunk_idx = if let Some(hunk_idx) = hunk_idx {
+ hunk_idx
+ } else {
+ return range;
+ };
+ let hunk = hunks.nth_hunk(hunk_idx);
+
+ let hunk_start = doc_text.line_to_char(hunk.after.start as usize);
+ let hunk_end = if hunk.after.is_empty() {
+ hunk_start + 1
+ } else {
+ doc_text.line_to_char(hunk.after.end as usize)
+ };
+ let new_range = Range::new(hunk_start, hunk_end);
+ if editor.mode == Mode::Select {
+ let head = if new_range.head < range.anchor {
+ new_range.anchor
+ } else {
+ new_range.head
+ };
+
+ Range::new(range.anchor, head)
+ } else {
+ new_range.with_direction(direction)
+ }
+ });
+
+ doc.set_selection(view.id, selection)
+ };
+ motion(cx.editor);
+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
+}
+
pub mod insert {
use super::*;
pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
@@ -4515,6 +4614,27 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
)
};
+ if ch == 'g' && doc.diff_handle().is_none() {
+ editor.set_status("Diff is not available in current buffer");
+ return;
+ }
+
+ let textobject_change = |range: Range| -> Range {
+ let diff_handle = doc.diff_handle().unwrap();
+ let hunks = diff_handle.hunks();
+ let line = range.cursor_line(text);
+ let hunk_idx = if let Some(hunk_idx) = hunks.hunk_at(line as u32, false) {
+ hunk_idx
+ } else {
+ return range;
+ };
+ let hunk = hunks.nth_hunk(hunk_idx).after;
+
+ let start = text.line_to_char(hunk.start as usize);
+ let end = text.line_to_char(hunk.end as usize);
+ Range::new(start, end).with_direction(range.direction())
+ };
+
let selection = doc.selection(view.id).clone().transform(|range| {
match ch {
'w' => textobject::textobject_word(text, range, objtype, count, false),
@@ -4528,6 +4648,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
'm' => textobject::textobject_pair_surround_closest(
text, range, objtype, count,
),
+ 'g' => textobject_change(range),
// TODO: cancel new ranges if inconsistent surround matches across lines
ch if !ch.is_ascii_alphanumeric() => {
textobject::textobject_pair_surround(text, range, objtype, ch, count)
diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs
index b6d9ea10..ebcd125a 100644
--- a/helix-term/src/keymap/default.rs
+++ b/helix-term/src/keymap/default.rs
@@ -100,6 +100,8 @@ pub fn default() -> HashMap<Mode, Keymap> {
"[" => { "Left bracket"
"d" => goto_prev_diag,
"D" => goto_first_diag,
+ "g" => goto_prev_change,
+ "G" => goto_first_change,
"f" => goto_prev_function,
"t" => goto_prev_class,
"a" => goto_prev_parameter,
@@ -111,6 +113,8 @@ pub fn default() -> HashMap<Mode, Keymap> {
"]" => { "Right bracket"
"d" => goto_next_diag,
"D" => goto_last_diag,
+ "g" => goto_next_change,
+ "G" => goto_last_change,
"f" => goto_next_function,
"t" => goto_next_class,
"a" => goto_next_parameter,