@@ -3,6 +3,7 @@ pub(crate) mod lsp;
3
3
pub ( crate ) mod typed;
4
4
5
5
pub use dap:: * ;
6
+ use helix_vcs:: Hunk ;
6
7
pub use lsp:: * ;
7
8
use tui:: text:: Spans ;
8
9
pub use typed:: * ;
@@ -308,6 +309,10 @@ impl MappableCommand {
308
309
goto_last_diag, "Goto last diagnostic" ,
309
310
goto_next_diag, "Goto next diagnostic" ,
310
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
316
goto_line_start, "Goto line start" ,
312
317
goto_line_end, "Goto line end" ,
313
318
goto_next_buffer, "Goto next buffer" ,
@@ -2912,6 +2917,100 @@ fn goto_prev_diag(cx: &mut Context) {
2912
2917
goto_pos ( editor, pos) ;
2913
2918
}
2914
2919
2920
+ fn goto_first_change ( cx : & mut Context ) {
2921
+ goto_first_change_impl ( cx, false ) ;
2922
+ }
2923
+
2924
+ fn goto_last_change ( cx : & mut Context ) {
2925
+ goto_first_change_impl ( cx, true ) ;
2926
+ }
2927
+
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 ( ) {
2932
+ let hunk = {
2933
+ let hunks = handle. hunks ( ) ;
2934
+ let idx = if reverse {
2935
+ hunks. len ( ) . saturating_sub ( 1 )
2936
+ } else {
2937
+ 0
2938
+ } ;
2939
+ hunks. nth_hunk ( idx)
2940
+ } ;
2941
+ if hunk != Hunk :: NONE {
2942
+ let pos = doc. text ( ) . line_to_char ( hunk. after . start as usize ) ;
2943
+ goto_pos ( editor, pos)
2944
+ }
2945
+ }
2946
+ }
2947
+
2948
+ fn goto_next_change ( cx : & mut Context ) {
2949
+ goto_next_change_impl ( cx, Direction :: Forward )
2950
+ }
2951
+
2952
+ fn goto_prev_change ( cx : & mut Context ) {
2953
+ goto_next_change_impl ( cx, Direction :: Backward )
2954
+ }
2955
+
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 ( ) {
2962
+ diff_handle
2963
+ } else {
2964
+ editor. set_status ( "Diff is not available in current buffer" ) ;
2965
+ return ;
2966
+ } ;
2967
+
2968
+ let selection = doc. selection ( view. id ) . clone ( ) . transform ( |range| {
2969
+ let cursor_line = range. cursor_line ( doc_text) as u32 ;
2970
+
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) ) ,
2979
+ } ;
2980
+ // TODO refactor with let..else once MSRV reaches 1.65
2981
+ let hunk_idx = if let Some ( hunk_idx) = hunk_idx {
2982
+ hunk_idx
2983
+ } else {
2984
+ return range;
2985
+ } ;
2986
+ let hunk = hunks. nth_hunk ( hunk_idx) ;
2987
+
2988
+ let hunk_start = doc_text. line_to_char ( hunk. after . start as usize ) ;
2989
+ let hunk_end = if hunk. after . is_empty ( ) {
2990
+ hunk_start + 1
2991
+ } else {
2992
+ doc_text. line_to_char ( hunk. after . end as usize )
2993
+ } ;
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 {
2997
+ new_range. anchor
2998
+ } else {
2999
+ new_range. head
3000
+ } ;
3001
+
3002
+ Range :: new ( range. anchor , head)
3003
+ } else {
3004
+ new_range. with_direction ( direction)
3005
+ }
3006
+ } ) ;
3007
+
3008
+ doc. set_selection ( view. id , selection)
3009
+ } ;
3010
+ motion ( cx. editor ) ;
3011
+ cx. editor . last_motion = Some ( Motion ( Box :: new ( motion) ) ) ;
3012
+ }
3013
+
2915
3014
pub mod insert {
2916
3015
use super :: * ;
2917
3016
pub type Hook = fn ( & Rope , & Selection , char ) -> Option < Transaction > ;
@@ -4515,6 +4614,27 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4515
4614
)
4516
4615
} ;
4517
4616
4617
+ if ch == 'g' && doc. diff_handle ( ) . is_none ( ) {
4618
+ editor. set_status ( "Diff is not available in current buffer" ) ;
4619
+ return ;
4620
+ }
4621
+
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 ) {
4627
+ hunk_idx
4628
+ } else {
4629
+ return range;
4630
+ } ;
4631
+ let hunk = hunks. nth_hunk ( hunk_idx) . after ;
4632
+
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 ( ) )
4636
+ } ;
4637
+
4518
4638
let selection = doc. selection ( view. id ) . clone ( ) . transform ( |range| {
4519
4639
match ch {
4520
4640
'w' => textobject:: textobject_word ( text, range, objtype, count, false ) ,
@@ -4528,6 +4648,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4528
4648
'm' => textobject:: textobject_pair_surround_closest (
4529
4649
text, range, objtype, count,
4530
4650
) ,
4651
+ 'g' => textobject_change ( range) ,
4531
4652
// TODO: cancel new ranges if inconsistent surround matches across lines
4532
4653
ch if !ch. is_ascii_alphanumeric ( ) => {
4533
4654
textobject:: textobject_pair_surround ( text, range, objtype, ch, count)
0 commit comments