@@ -3,6 +3,7 @@ pub(crate) mod lsp;
3pub(crate) mod typed;
3pub(crate) mod typed;
5pub use dap::*;
5pub use dap::*;
6pub use lsp::*;
7pub use lsp::*;
7use tui::text::Spans;
8use tui::text::Spans;
8pub use typed::*;
9pub use typed::*;
@@ -308,6 +309,10 @@ impl MappableCommand {
308 goto_last_diag, "Goto last diagnostic",
309 goto_last_diag, "Goto last diagnostic",
309 goto_next_diag, "Goto next diagnostic",
310 goto_next_diag, "Goto next diagnostic",
310 goto_prev_diag, "Goto previous diagnostic",
311 goto_prev_diag, "Goto previous diagnostic",
312+ goto_next_change, "Goto next change",
313+ goto_prev_change, "Goto previous change",
314+ goto_first_change, "Goto first change",
315+ goto_last_change, "Goto last change",
311 goto_line_start, "Goto line start",
316 goto_line_start, "Goto line start",
312 goto_line_end, "Goto line end",
317 goto_line_end, "Goto line end",
313 goto_next_buffer, "Goto next buffer",
318 goto_next_buffer, "Goto next buffer",
@@ -2912,6 +2917,100 @@ fn goto_prev_diag(cx: &mut Context) {
2912 goto_pos(editor, pos);
2917 goto_pos(editor, pos);
2920+fn goto_first_change(cx: &mut Context) {
2921+ goto_first_change_impl(cx, false);
2924+fn goto_last_change(cx: &mut Context) {
2925+ goto_first_change_impl(cx, true);
2928+fn goto_first_change_impl(cx: &mut Context, reverse: bool) {
2929+ let editor = &mut cx.editor;
2930+ let (_, doc) = current!(editor);
2931+ if let Some(handle) = doc.diff_handle() {
2933+ let hunks = handle.hunks();
2934+ let idx = if reverse {
2935+ hunks.len().saturating_sub(1)
2939+ hunks.nth_hunk(idx)
2941+ if hunk != Hunk::NONE {
2942+ let pos = doc.text().line_to_char(hunk.after.start as usize);
2943+ goto_pos(editor, pos)
2948+fn goto_next_change(cx: &mut Context) {
2949+ goto_next_change_impl(cx, Direction::Forward)
2952+fn goto_prev_change(cx: &mut Context) {
2953+ goto_next_change_impl(cx, Direction::Backward)
2956+fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
2957+ let count = cx.count() as u32 - 1;
2958+ let motion = move |editor: &mut Editor| {
2959+ let (view, doc) = current!(editor);
2960+ let doc_text = doc.text().slice(..);
2961+ let diff_handle = if let Some(diff_handle) = doc.diff_handle() {
2964+ editor.set_status("Diff is not available in current buffer");
2968+ let selection = doc.selection(view.id).clone().transform(|range| {
2969+ let cursor_line = range.cursor_line(doc_text) as u32;
2971+ let hunks = diff_handle.hunks();
2972+ let hunk_idx = match direction {
2973+ Direction::Forward => hunks
2974+ .next_hunk(cursor_line)
2975+ .map(|idx| (idx + count).min(hunks.len() - 1)),
2976+ Direction::Backward => hunks
2977+ .prev_hunk(cursor_line)
2978+ .map(|idx| idx.saturating_sub(count)),
2980+ // TODO refactor with let..else once MSRV reaches 1.65
2981+ let hunk_idx = if let Some(hunk_idx) = hunk_idx {
2986+ let hunk = hunks.nth_hunk(hunk_idx);
2988+ let hunk_start = doc_text.line_to_char(hunk.after.start as usize);
2989+ let hunk_end = if hunk.after.is_empty() {
2992+ doc_text.line_to_char(hunk.after.end as usize)
2994+ let new_range = Range::new(hunk_start, hunk_end);
2995+ if editor.mode == Mode::Select {
2996+ let head = if new_range.head < range.anchor {
3002+ Range::new(range.anchor, head)
3004+ new_range.with_direction(direction)
3008+ doc.set_selection(view.id, selection)
3011+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
2915pub mod insert {
3014pub mod insert {
2916 use super::*;
3015 use super::*;
2917 pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
3016 pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
@@ -4515,6 +4614,27 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4617+ if ch == 'g' && doc.diff_handle().is_none() {
4618+ editor.set_status("Diff is not available in current buffer");
4622+ let textobject_change = |range: Range| -> Range {
4623+ let diff_handle = doc.diff_handle().unwrap();
4624+ let hunks = diff_handle.hunks();
4625+ let line = range.cursor_line(text);
4626+ let hunk_idx = if let Some(hunk_idx) = hunks.hunk_at(line as u32, false) {
4631+ let hunk = hunks.nth_hunk(hunk_idx).after;
4633+ let start = text.line_to_char(hunk.start as usize);
4634+ let end = text.line_to_char(hunk.end as usize);
4635+ Range::new(start, end).with_direction(range.direction())
4518 let selection = doc.selection(view.id).clone().transform(|range| {
4638 let selection = doc.selection(view.id).clone().transform(|range| {
4519 match ch {
4639 match ch {
4520 'w' => textobject::textobject_word(text, range, objtype, count, false),
4640 'w' => textobject::textobject_word(text, range, objtype, count, false),
@@ -4528,6 +4648,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4528 'm' => textobject::textobject_pair_surround_closest(
4648 'm' => textobject::textobject_pair_surround_closest(
4529 text, range, objtype, count,
4649 text, range, objtype, count,
4651+ 'g' => textobject_change(range),
4531 // TODO: cancel new ranges if inconsistent surround matches across lines
4652 // TODO: cancel new ranges if inconsistent surround matches across lines
4532 ch if !ch.is_ascii_alphanumeric() => {
4653 ch if !ch.is_ascii_alphanumeric() => {
4533 textobject::textobject_pair_surround(text, range, objtype, ch, count)
4654 textobject::textobject_pair_surround(text, range, objtype, ch, count)