diff --git a/pkg/gui/controllers/patch_explorer_controller.go b/pkg/gui/controllers/patch_explorer_controller.go index 5b832c80d..7f0a88c0f 100644 --- a/pkg/gui/controllers/patch_explorer_controller.go +++ b/pkg/gui/controllers/patch_explorer_controller.go @@ -223,13 +223,13 @@ func (self *PatchExplorerController) HandleNextLineRange() error { } func (self *PatchExplorerController) HandlePrevHunk() error { - self.context.GetState().CycleHunk(false) + self.context.GetState().SelectPreviousHunk() return nil } func (self *PatchExplorerController) HandleNextHunk() error { - self.context.GetState().CycleHunk(true) + self.context.GetState().SelectNextHunk() return nil } diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index 980f01fda..acf2b0ef2 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -125,6 +125,11 @@ func (s *State) ToggleSelectHunk() { s.selectMode = LINE } else { s.selectMode = HUNK + + // If we are not currently on a change line, select the next one (or the + // previous one if there is no next one): + s.selectedLineIdx = s.viewLineIndices[s.patch.GetNextChangeIdx( + s.patchLineIndices[s.selectedLineIdx])] } } @@ -203,25 +208,49 @@ func (s *State) DragSelectLine(newSelectedLineIdx int) { func (s *State) CycleSelection(forward bool) { if s.SelectingHunk() { - s.CycleHunk(forward) + if forward { + s.SelectNextHunk() + } else { + s.SelectPreviousHunk() + } } else { s.CycleLine(forward) } } -func (s *State) CycleHunk(forward bool) { - change := 1 - if !forward { - change = -1 +func (s *State) SelectPreviousHunk() { + patchLines := s.patch.Lines() + patchLineIdx := s.patchLineIndices[s.selectedLineIdx] + nextNonChangeLine := patchLineIdx + for nextNonChangeLine >= 0 && patchLines[nextNonChangeLine].IsChange() { + nextNonChangeLine-- } - - hunkIdx := s.patch.HunkContainingLine(s.patchLineIndices[s.selectedLineIdx]) - if hunkIdx != -1 { - newHunkIdx := hunkIdx + change - if newHunkIdx >= 0 && newHunkIdx < s.patch.HunkCount() { - start := s.patch.HunkStartIdx(newHunkIdx) - s.selectedLineIdx = s.viewLineIndices[s.patch.GetNextChangeIdx(start)] + nextChangeLine := nextNonChangeLine + for nextChangeLine >= 0 && !patchLines[nextChangeLine].IsChange() { + nextChangeLine-- + } + if nextChangeLine >= 0 { + // Now we found a previous hunk, but we're on its last line. Skip to the beginning. + for nextChangeLine > 0 && patchLines[nextChangeLine-1].IsChange() { + nextChangeLine-- } + s.selectedLineIdx = s.viewLineIndices[nextChangeLine] + } +} + +func (s *State) SelectNextHunk() { + patchLines := s.patch.Lines() + patchLineIdx := s.patchLineIndices[s.selectedLineIdx] + nextNonChangeLine := patchLineIdx + for nextNonChangeLine < len(patchLines) && patchLines[nextNonChangeLine].IsChange() { + nextNonChangeLine++ + } + nextChangeLine := nextNonChangeLine + for nextChangeLine < len(patchLines) && !patchLines[nextChangeLine].IsChange() { + nextChangeLine++ + } + if nextChangeLine < len(patchLines) { + s.selectedLineIdx = s.viewLineIndices[nextChangeLine] } } @@ -259,11 +288,34 @@ func (s *State) CurrentHunkBounds() (int, int) { return start, end } +func (s *State) selectionRangeForCurrentBlockOfChanges() (int, int) { + patchLines := s.patch.Lines() + patchLineIdx := s.patchLineIndices[s.selectedLineIdx] + + patchStart := patchLineIdx + for patchStart > 0 && patchLines[patchStart-1].IsChange() { + patchStart-- + } + + patchEnd := patchLineIdx + for patchEnd < len(patchLines)-1 && patchLines[patchEnd+1].IsChange() { + patchEnd++ + } + + viewStart, viewEnd := s.viewLineIndices[patchStart], s.viewLineIndices[patchEnd] + + // Increase viewEnd in case the last patch line is wrapped to more than one view line. + for viewEnd < len(s.patchLineIndices)-1 && s.patchLineIndices[viewEnd] == s.patchLineIndices[viewEnd+1] { + viewEnd++ + } + + return viewStart, viewEnd +} + func (s *State) SelectedViewRange() (int, int) { switch s.selectMode { case HUNK: - start, end := s.CurrentHunkBounds() - return s.viewLineIndices[start], s.viewLineIndices[end] + return s.selectionRangeForCurrentBlockOfChanges() case RANGE: if s.rangeStartLineIdx > s.selectedLineIdx { return s.selectedLineIdx, s.rangeStartLineIdx diff --git a/pkg/integration/tests/patch_building/specific_selection.go b/pkg/integration/tests/patch_building/specific_selection.go index 5b4f62a03..98cec4d1c 100644 --- a/pkg/integration/tests/patch_building/specific_selection.go +++ b/pkg/integration/tests/patch_building/specific_selection.go @@ -55,31 +55,13 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Main.ToggleSelectHunk). SelectedLines( - Contains(`@@ -1,6 +1,6 @@`), Contains(`-1a`), Contains(`+aa`), - Contains(` 1b`), - Contains(`-1c`), - Contains(`+cc`), - Contains(` 1d`), - Contains(` 1e`), - Contains(` 1f`), ). PressPrimaryAction(). SelectedLines( - 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`), + Contains(`-1c`), + Contains(`+cc`), ). Tap(func() { t.Views().Information().Content(Contains("Building patch")) @@ -154,8 +136,7 @@ var SpecificSelection = NewIntegrationTest(NewIntegrationTestArgs{ Contains(`-1a`), Contains(`+aa`), Contains(` 1b`), - Contains(`-1c`), - Contains(`+cc`), + Contains(` 1c`), Contains(` 1d`), Contains(` 1e`), Contains(` 1f`), diff --git a/pkg/integration/tests/staging/diff_context_change.go b/pkg/integration/tests/staging/diff_context_change.go index a3f7b481f..bd4de6daa 100644 --- a/pkg/integration/tests/staging/diff_context_change.go +++ b/pkg/integration/tests/staging/diff_context_change.go @@ -52,67 +52,40 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{ IsFocused(). Press(keys.Main.ToggleSelectHunk). SelectedLines( - Contains(`@@ -1,6 +1,6 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), - Contains(` 6a`), ). Press(keys.Universal.IncreaseContextInDiffView). Tap(func() { t.ExpectToast(Equals("Changed diff context size to 4")) }). SelectedLines( - Contains(`@@ -1,7 +1,7 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), - Contains(` 6a`), - Contains(` 7a`), ). Press(keys.Universal.DecreaseContextInDiffView). Tap(func() { t.ExpectToast(Equals("Changed diff context size to 3")) }). SelectedLines( - Contains(`@@ -1,6 +1,6 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), - Contains(` 6a`), ). Press(keys.Universal.DecreaseContextInDiffView). Tap(func() { t.ExpectToast(Equals("Changed diff context size to 2")) }). SelectedLines( - Contains(`@@ -1,5 +1,5 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), ). Press(keys.Universal.DecreaseContextInDiffView). Tap(func() { t.ExpectToast(Equals("Changed diff context size to 1")) }). SelectedLines( - Contains(`@@ -2,3 +2,3 @@`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), ). PressPrimaryAction(). Press(keys.Universal.TogglePanel) @@ -121,18 +94,14 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{ IsFocused(). Press(keys.Main.ToggleSelectHunk). SelectedLines( - Contains(`@@ -2,3 +2,3 @@`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), ). Press(keys.Universal.DecreaseContextInDiffView). Tap(func() { t.ExpectToast(Equals("Changed diff context size to 0")) }). SelectedLines( - Contains(`@@ -3,1 +3 @@`), Contains(`-3a`), Contains(`+3b`), ). @@ -141,24 +110,16 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{ t.ExpectToast(Equals("Changed diff context size to 1")) }). SelectedLines( - Contains(`@@ -2,3 +2,3 @@`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), ). Press(keys.Universal.IncreaseContextInDiffView). Tap(func() { t.ExpectToast(Equals("Changed diff context size to 2")) }). SelectedLines( - Contains(`@@ -1,5 +1,5 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), ) }, }) diff --git a/pkg/integration/tests/staging/stage_hunks.go b/pkg/integration/tests/staging/stage_hunks.go index 1d05ed429..8acd39388 100644 --- a/pkg/integration/tests/staging/stage_hunks.go +++ b/pkg/integration/tests/staging/stage_hunks.go @@ -11,11 +11,10 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ Skip: false, SetupConfig: func(config *config.AppConfig) {}, SetupRepo: func(shell *Shell) { - // need to be working with a few lines so that git perceives it as two separate hunks - shell.CreateFileAndAdd("file1", "1a\n2a\n3a\n4a\n5a\n6a\n7a\n8a\n9a\n10a\n11a\n12a\n13a\n14a\n15a") + shell.CreateFileAndAdd("file1", "1a\n2a\n3a\n4a\n5a\n6a\n7a\n8a") shell.Commit("one") - shell.UpdateFile("file1", "1a\n2a\n3b\n4a\n5a\n6a\n7a\n8a\n9a\n10a\n11a\n12a\n13b\n14a\n15a") + shell.UpdateFile("file1", "1a\n2a\n3b\n4a\n5a\n6b\n7a\n8a") // hunk looks like: // diff --git a/file1 b/file1 @@ -29,15 +28,10 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ // +3b // 4a // 5a - // 6a - // @@ -10,6 +10,6 @@ - // 10a - // 11a - // 12a - // -13a - // +13b - // 14a - // 15a + // -6a + // +6b + // 7a + // 8a // \ No newline at end of file }, Run: func(t *TestDriver, keys config.KeybindingConfig) { @@ -55,43 +49,23 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Universal.NextBlock). SelectedLines( - Contains("-13a"), + Contains("-6a"), ). Press(keys.Main.ToggleSelectHunk). SelectedLines( - Contains("@@ -10,6 +10,6 @@"), - Contains(" 10a"), - Contains(" 11a"), - Contains(" 12a"), - Contains("-13a"), - Contains("+13b"), - Contains(" 14a"), - Contains(" 15a"), - Contains(`\ No newline at end of file`), + Contains("-6a"), + Contains("+6b"), ). // when in hunk mode, pressing up/down moves us up/down by a hunk SelectPreviousItem(). SelectedLines( - Contains(`@@ -1,6 +1,6 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), - Contains(` 6a`), ). SelectNextItem(). SelectedLines( - Contains("@@ -10,6 +10,6 @@"), - Contains(" 10a"), - Contains(" 11a"), - Contains(" 12a"), - Contains("-13a"), - Contains("+13b"), - Contains(" 14a"), - Contains(" 15a"), - Contains(`\ No newline at end of file`), + Contains("-6a"), + Contains("+6b"), ). // stage the second hunk PressPrimaryAction(). @@ -102,8 +76,8 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ Tap(func() { t.Views().StagingSecondary(). ContainsLines( - Contains("-13a"), - Contains("+13b"), + Contains("-6a"), + Contains("+6b"), ) }). Press(keys.Universal.TogglePanel) @@ -112,11 +86,11 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ IsFocused(). // after toggling panel, we're back to only having selected a single line SelectedLines( - Contains("-13a"), + Contains("-6a"), ). PressPrimaryAction(). SelectedLines( - Contains("+13b"), + Contains("+6b"), ). PressPrimaryAction(). IsEmpty() @@ -128,14 +102,8 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Main.ToggleSelectHunk). SelectedLines( - Contains(`@@ -1,6 +1,6 @@`), - Contains(` 1a`), - Contains(` 2a`), Contains(`-3a`), Contains(`+3b`), - Contains(` 4a`), - Contains(` 5a`), - Contains(` 6a`), ). Press(keys.Universal.Remove). Tap(func() { @@ -143,15 +111,8 @@ var StageHunks = NewIntegrationTest(NewIntegrationTestArgs{ }). Content(DoesNotContain("-3a").DoesNotContain("+3b")). SelectedLines( - Contains("@@ -10,6 +10,6 @@"), - Contains(" 10a"), - Contains(" 11a"), - Contains(" 12a"), - Contains("-13a"), - Contains("+13b"), - Contains(" 14a"), - Contains(" 15a"), - Contains(`\ No newline at end of file`), + Contains("-6a"), + Contains("+6b"), ) }, })