@@ -35,6 +35,8 @@ function ProjectDiffScreen:constructor(opts)
3535 { ' Stage hunk' , keymaps [' buffer_hunk_stage' ] },
3636 { ' Unstage hunk' , keymaps [' buffer_hunk_unstage' ] },
3737 { ' Reset hunk' , keymaps [' buffer_hunk_reset' ] },
38+ { ' Next' , keymaps [' next' ] },
39+ { ' Previous' , keymaps [' previous' ] },
3840 { ' Stage all' , keymaps [' stage_all' ] },
3941 { ' Unstage all' , keymaps [' unstage_all' ] },
4042 { ' Reset all' , keymaps [' reset_all' ] },
@@ -81,16 +83,6 @@ function ProjectDiffScreen:constructor(opts)
8183 }
8284end
8385
84- function ProjectDiffScreen :hunk_up ()
85- local hunk_alignment = project_diff_preview_setting :get (' hunk_alignment' )
86- self .diff_view :prev (hunk_alignment )
87- end
88-
89- function ProjectDiffScreen :hunk_down ()
90- local hunk_alignment = project_diff_preview_setting :get (' hunk_alignment' )
91- self .diff_view :next (hunk_alignment )
92- end
93-
9486function ProjectDiffScreen :move_to (query_fn )
9587 return self .status_list_view :move_to (query_fn )
9688end
@@ -422,6 +414,100 @@ function ProjectDiffScreen:handle_list_move()
422414 self .diff_view :move_to_hunk (nil , hunk_alignment )
423415end
424416
417+ function ProjectDiffScreen :get_current_mark_index ()
418+ loop .free_textlock ()
419+ local diff = self .model :get_diff ()
420+ if not diff or not diff .marks or # diff .marks == 0 then
421+ return nil , 0
422+ end
423+
424+ local marks = diff .marks
425+ local lnum = self .diff_view .scene :get (' current' ):get_lnum ()
426+
427+ for i , mark in ipairs (marks ) do
428+ if lnum >= mark .top and lnum <= mark .bot then
429+ return i , # marks
430+ elseif mark .top > lnum then
431+ return math.max (1 , i - 1 ), # marks
432+ end
433+ end
434+
435+ return # marks , # marks
436+ end
437+
438+ function ProjectDiffScreen :move_to_next_file ()
439+ loop .free_textlock ()
440+ local component = self .status_list_view .scene :get (' list' )
441+ local current_lnum = component :get_lnum ()
442+ local count = component :get_line_count ()
443+
444+ -- Find next file entry (skip folders)
445+ for offset = 1 , count do
446+ local target_lnum = current_lnum + offset
447+ if target_lnum > count then target_lnum = target_lnum - count end
448+
449+ local item = self .status_list_view :get_list_item (target_lnum )
450+ if item and item .entry and item .entry .status then
451+ component :unlock ():set_lnum (target_lnum ):lock ()
452+ return item
453+ end
454+ end
455+ return nil
456+ end
457+
458+ function ProjectDiffScreen :move_to_prev_file ()
459+ loop .free_textlock ()
460+ local component = self .status_list_view .scene :get (' list' )
461+ local current_lnum = component :get_lnum ()
462+ local count = component :get_line_count ()
463+
464+ -- Find previous file entry (skip folders)
465+ for offset = 1 , count do
466+ local target_lnum = current_lnum - offset
467+ if target_lnum < 1 then target_lnum = target_lnum + count end
468+
469+ local item = self .status_list_view :get_list_item (target_lnum )
470+ if item and item .entry and item .entry .status then
471+ component :unlock ():set_lnum (target_lnum ):lock ()
472+ return item
473+ end
474+ end
475+ return nil
476+ end
477+
478+ function ProjectDiffScreen :next_hunk ()
479+ local current_index , total_hunks = self :get_current_mark_index ()
480+ local hunk_alignment = project_diff_preview_setting :get (' hunk_alignment' )
481+
482+ if not current_index or total_hunks == 0 or current_index >= total_hunks then
483+ -- At last hunk or no hunks - move to next file
484+ local list_item = self :move_to_next_file ()
485+ if not list_item then return end
486+ self .model :set_entry_id (list_item .id )
487+ self .diff_view :render ()
488+ self .diff_view :move_to_hunk (1 , hunk_alignment )
489+ else
490+ self .diff_view :next (hunk_alignment )
491+ end
492+ end
493+
494+ function ProjectDiffScreen :prev_hunk ()
495+ local current_index , total_hunks = self :get_current_mark_index ()
496+ local hunk_alignment = project_diff_preview_setting :get (' hunk_alignment' )
497+
498+ if not current_index or total_hunks == 0 or current_index <= 1 then
499+ -- At first hunk or no hunks - move to previous file's last hunk
500+ local list_item = self :move_to_prev_file ()
501+ if not list_item then return end
502+ self .model :set_entry_id (list_item .id )
503+ self .diff_view :render ()
504+ -- Pass 0 to go to last hunk (move_to_hunk clamps <1 to #marks)
505+ self .diff_view :move_to_hunk (0 , hunk_alignment )
506+ else
507+ self .diff_view :prev (hunk_alignment )
508+ end
509+ end
510+
425511function ProjectDiffScreen :focus_relative_buffer_entry (buffer )
426512 local filename = buffer :get_relative_name ()
427513
@@ -523,6 +609,30 @@ function ProjectDiffScreen:setup_list_keymaps()
523609 self :toggle_focus ()
524610 end ,
525611 },
612+ {
613+ mode = ' n' ,
614+ mapping = keymaps .next ,
615+ handler = loop .debounce_coroutine (function ()
616+ local list_item = self :move_to_next_file ()
617+ if not list_item then return end
618+ self .model :set_entry_id (list_item .id )
619+ local hunk_alignment = project_diff_preview_setting :get (' hunk_alignment' )
620+ self .diff_view :render ()
621+ self .diff_view :move_to_hunk (1 , hunk_alignment )
622+ end , 15 ),
623+ },
624+ {
625+ mode = ' n' ,
626+ mapping = keymaps .previous ,
627+ handler = loop .debounce_coroutine (function ()
628+ local list_item = self :move_to_prev_file ()
629+ if not list_item then return end
630+ self .model :set_entry_id (list_item .id )
631+ local hunk_alignment = project_diff_preview_setting :get (' hunk_alignment' )
632+ self .diff_view :render ()
633+ self .diff_view :move_to_hunk (0 , hunk_alignment )
634+ end , 15 ),
635+ },
526636 })
527637end
528638
@@ -564,6 +674,12 @@ function ProjectDiffScreen:setup_diff_keymaps()
564674 enter = loop .coroutine (function ()
565675 self :enter_view ()
566676 end ),
677+ next_hunk = loop .debounce_coroutine (function ()
678+ self :next_hunk ()
679+ end , 15 ),
680+ prev_hunk = loop .debounce_coroutine (function ()
681+ self :prev_hunk ()
682+ end , 15 ),
567683 }
568684
569685 self .diff_keymaps = handlers
@@ -626,6 +742,16 @@ function ProjectDiffScreen:setup_diff_keymaps()
626742 self :toggle_focus ()
627743 end ,
628744 },
745+ {
746+ mode = ' n' ,
747+ mapping = keymaps .next ,
748+ handler = handlers .next_hunk ,
749+ },
750+ {
751+ mode = ' n' ,
752+ mapping = keymaps .previous ,
753+ handler = handlers .prev_hunk ,
754+ },
629755 {
630756 mode = ' n' ,
631757 mapping = {
0 commit comments