diff --git a/pkg/commands/patch/patch.go b/pkg/commands/patch/patch.go index f5efe95da..343eea54f 100644 --- a/pkg/commands/patch/patch.go +++ b/pkg/commands/patch/patch.go @@ -115,15 +115,22 @@ func (self *Patch) HunkContainingLine(idx int) int { return -1 } -// Returns the patch line index of the next change (i.e. addition or deletion). -func (self *Patch) GetNextChangeIdx(idx int) int { +// Returns the patch line index of the next change (i.e. addition or deletion) +// that matches the same "included" state, given the includedLines. If you don't +// care about included states, pass nil for includedLines and false for included. +func (self *Patch) GetNextChangeIdxOfSameIncludedState(idx int, includedLines []int, included bool) (int, bool) { idx = lo.Clamp(idx, 0, self.LineCount()-1) lines := self.Lines() + isMatch := func(i int, line *PatchLine) bool { + sameIncludedState := lo.Contains(includedLines, i) == included + return line.IsChange() && sameIncludedState + } + for i, line := range lines[idx:] { - if line.isChange() { - return i + idx + if isMatch(i+idx, line) { + return i + idx, true } } @@ -131,13 +138,18 @@ func (self *Patch) GetNextChangeIdx(idx int) int { // return the index of the last change for i := len(lines) - 1; i >= 0; i-- { line := lines[i] - if line.isChange() { - return i + if isMatch(i, line) { + return i, true } } - // should not be possible - return 0 + return 0, false +} + +// Returns the patch line index of the next change (i.e. addition or deletion). +func (self *Patch) GetNextChangeIdx(idx int) int { + result, _ := self.GetNextChangeIdxOfSameIncludedState(idx, nil, false) + return result } // Returns the length of the patch in lines diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go index 87328a15a..faddbd5f8 100644 --- a/pkg/gui/controllers/patch_building_controller.go +++ b/pkg/gui/controllers/patch_building_controller.go @@ -161,6 +161,8 @@ func (self *PatchBuildingController) toggleSelection() error { state.SetLineSelectMode() } + state.SelectNextStageableLineOfSameIncludedState(self.context().GetIncludedLineIndices(), firstSelectedChangeLineIsStaged) + return nil } diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index 8c308b3d0..980f01fda 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -338,3 +338,11 @@ func wrapPatchLines(diff string, view *gocui.View) ([]int, []int) { view.Wrap, view.Editable, strings.TrimSuffix(diff, "\n"), view.InnerWidth(), view.TabWidth) return viewLineIndices, patchLineIndices } + +func (s *State) SelectNextStageableLineOfSameIncludedState(includedLines []int, included bool) { + _, lastLineIdx := s.SelectedPatchRange() + patchLineIdx, found := s.patch.GetNextChangeIdxOfSameIncludedState(lastLineIdx+1, includedLines, included) + if found { + s.SelectLine(s.viewLineIndices[patchLineIdx]) + } +} diff --git a/pkg/integration/tests/patch_building/move_to_index_partial.go b/pkg/integration/tests/patch_building/move_to_index_partial.go index 827a3aedb..409f86a57 100644 --- a/pkg/integration/tests/patch_building/move_to_index_partial.go +++ b/pkg/integration/tests/patch_building/move_to_index_partial.go @@ -48,7 +48,6 @@ var MoveToIndexPartial = NewIntegrationTest(NewIntegrationTestArgs{ Contains(`+third line2`), ). PressPrimaryAction(). - SelectNextItem(). PressPrimaryAction(). Tap(func() { t.Views().Information().Content(Contains("Building patch")) diff --git a/pkg/integration/tests/patch_building/specific_selection.go b/pkg/integration/tests/patch_building/specific_selection.go index 9027a34e6..5b4f62a03 100644 --- a/pkg/integration/tests/patch_building/specific_selection.go +++ b/pkg/integration/tests/patch_building/specific_selection.go @@ -66,18 +66,20 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ Contains(` 1f`), ). PressPrimaryAction(). - // unlike in the staging panel, we don't remove lines from the patch building panel - // upon 'adding' them. So the same lines will be selected SelectedLines( - Contains(`@@ -1,6 +1,6 @@`), - Contains(`-1a`), - Contains(`+aa`), - Contains(` 1b`), - Contains(`-1c`), - Contains(`+cc`), - Contains(` 1d`), - Contains(` 1e`), - Contains(` 1f`), + Contains(`@@ -17,9 +17,9 @@`), + Contains(` 1q`), + Contains(` 1r`), + Contains(` 1s`), + Contains(`-1t`), + Contains(`-1u`), + Contains(`-1v`), + Contains(`+tt`), + Contains(`+uu`), + Contains(`+vv`), + Contains(` 1w`), + Contains(` 1x`), + Contains(` 1y`), ). Tap(func() { t.Views().Information().Content(Contains("Building patch")) @@ -106,12 +108,21 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ Contains("+2a"), ). PressPrimaryAction(). + SelectedLines( + Contains("+2b"), + ). NavigateToLine(Contains("+2c")). Press(keys.Universal.ToggleRangeSelect). NavigateToLine(Contains("+2e")). PressPrimaryAction(). + SelectedLines( + Contains("+2f"), + ). NavigateToLine(Contains("+2g")). PressPrimaryAction(). + SelectedLines( + Contains("+2h"), + ). Tap(func() { t.Views().Information().Content(Contains("Building patch"))