diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 8668277c6..b7afdf918 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -67,8 +67,8 @@ func (gui *Gui) resetHelpersAndControllers() { worktreeHelper, searchHelper, ) - diffHelper := helpers.NewDiffHelper(helperCommon) diffLineHelper := helpers.NewDiffLineHelper(helperCommon) + diffHelper := helpers.NewDiffHelper(helperCommon, diffLineHelper) cherryPickHelper := helpers.NewCherryPickHelper( helperCommon, rebaseHelper, diff --git a/pkg/gui/controllers/helpers/diff_helper.go b/pkg/gui/controllers/helpers/diff_helper.go index 02d8f005c..0547fafa3 100644 --- a/pkg/gui/controllers/helpers/diff_helper.go +++ b/pkg/gui/controllers/helpers/diff_helper.go @@ -14,12 +14,14 @@ import ( ) type DiffHelper struct { - c *HelperCommon + c *HelperCommon + diffLineHelper *DiffLineHelper } -func NewDiffHelper(c *HelperCommon) *DiffHelper { +func NewDiffHelper(c *HelperCommon, diffLineHelper *DiffLineHelper) *DiffHelper { return &DiffHelper{ - c: c, + c: c, + diffLineHelper: diffLineHelper, } } @@ -107,6 +109,11 @@ func (self *DiffHelper) RenderToMainAgain() { if currentSide.GetKey() == currentKey || currentKey == context.NORMAL_MAIN_CONTEXT_KEY || currentKey == context.NORMAL_SECONDARY_CONTEXT_KEY { + // Whatever changed can make the diff come out differently, such as a new + // renderer laying it out its own way, so the line you were looking at could + // end up anywhere in the view; keep it in front of you. + self.diffLineHelper.PreserveDiffPositionOnRerender(self.c.Contexts().Normal.GetView()) + self.diffLineHelper.PreserveDiffPositionOnRerender(self.c.Contexts().NormalSecondary.GetView()) currentSide.HandleRenderToMain() } } diff --git a/pkg/integration/tests/main_view/keep_position_in_both_panes_when_switching_diff_renderers.go b/pkg/integration/tests/main_view/keep_position_in_both_panes_when_switching_diff_renderers.go new file mode 100644 index 000000000..9f562f7db --- /dev/null +++ b/pkg/integration/tests/main_view/keep_position_in_both_panes_when_switching_diff_renderers.go @@ -0,0 +1,78 @@ +package main_view + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var KeepPositionInBothPanesWhenSwitchingDiffRenderers = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Switching to another diff renderer keeps the place in the lower pane too, not only in the upper one", + ExtraCmdArgs: []string{}, + Skip: false, + Width: 120, + Height: 30, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false + // Renderers that speak the metadata protocol, so that focusing the main view + // keeps their rendering rather than falling back to git's own diff. + cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{ + {Name: "plain", Command: `printf '\033]1717;1\007'; cat`}, + // The same diff, three lines further down the view. (Lines before the + // diff's own header aren't part of it, so it still reads the same.) + {Name: "banner", Command: `printf '\033]1717;1\007'; printf 'rendered for you\n\n\n'; cat`}, + } + }, + SetupRepo: func(shell *Shell) { + lines := make([]string, 40) + for i := range lines { + lines[i] = fmt.Sprintf("line%02d", i+1) + } + shell.CreateFileAndAdd("file1", strings.Join(lines, "\n")+"\n") + shell.Commit("one") + + // Four staged changes to have a diff worth scrolling in the lower pane, and one + // unstaged one to split the file's diff across both panes. + for _, i := range []int{5, 15, 25, 35} { + lines[i-1] = strings.ToUpper(lines[i-1]) + } + shell.UpdateFile("file1", strings.Join(lines, "\n")+"\n") + shell.GitAddAll() + + lines[39] = strings.ToUpper(lines[39]) + shell.UpdateFile("file1", strings.Join(lines, "\n")+"\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + PressTab() + + t.Views().Secondary(). + IsFocused(). + Press(keys.Main.NextHunk). + Press(keys.Main.NextHunk). + Press(keys.Main.NextHunk). + SelectedLines( + Contains("-line35"), + ). + SelectedLineIdx(35). + OriginY(14). + Press(keys.Universal.CycleDiffRenderers). + Tap(func() { + t.ExpectToast(Equals("Diff renderer: banner (2 of 2)")) + }). + // The banner pushed the whole diff three lines down, and the lower pane came + // along with it, just as the upper one would have. + SelectedLines( + Contains("-line35"), + ). + SelectedLineIdx(38). + OriginY(17) + }, +}) diff --git a/pkg/integration/tests/main_view/keep_position_when_switching_diff_renderers.go b/pkg/integration/tests/main_view/keep_position_when_switching_diff_renderers.go new file mode 100644 index 000000000..7241ac9bf --- /dev/null +++ b/pkg/integration/tests/main_view/keep_position_when_switching_diff_renderers.go @@ -0,0 +1,75 @@ +package main_view + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var KeepPositionWhenSwitchingDiffRenderers = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Switching to another diff renderer keeps the line you were looking at where it was", + ExtraCmdArgs: []string{}, + Skip: false, + Width: 120, + Height: 30, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false + cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{ + {Name: "plain", Command: "cat"}, + // The same diff, three lines further down the view. (Lines before the + // diff's own header aren't part of it, so it still reads the same.) + {Name: "banner", Command: `printf 'rendered for you\n\n\n'; cat`}, + } + }, + SetupRepo: func(shell *Shell) { + lines := make([]string, 40) + for i := range lines { + lines[i] = fmt.Sprintf("line%02d", i+1) + } + shell.CreateFileAndAdd("file1", strings.Join(lines, "\n")+"\n") + shell.Commit("one") + + for _, i := range []int{5, 15, 25, 35} { + lines[i-1] = strings.ToUpper(lines[i-1]) + } + shell.UpdateFile("file1", strings.Join(lines, "\n")+"\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + Press(keys.Main.NextHunk). + Press(keys.Main.NextHunk). + Press(keys.Main.NextHunk). + SelectedLines( + Contains("-line35"), + ). + SelectedLineIdx(35). + OriginY(14). + Press(keys.Universal.CycleDiffRenderers). + Tap(func() { + t.ExpectToast(Equals("Diff renderer: banner (2 of 2)")) + }). + // The banner pushed the whole diff three lines down, and the view came + // along with it: the same line on the same screen row (38 - 17 = 21). + SelectedLines( + Contains("-line35"), + ). + SelectedLineIdx(38). + OriginY(17). + Press(keys.Universal.CycleDiffRenderers). + Tap(func() { + t.ExpectToast(Equals("Diff renderer: plain (1 of 2)")) + }). + SelectedLines( + Contains("-line35"), + ). + SelectedLineIdx(35). + OriginY(14) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index f224870a3..c9bc6a721 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -371,7 +371,9 @@ var tests = []*components.IntegrationTest{ main_view.FileNavigationScrollsToTheTop, main_view.HideSelectionWhenChangesVanish, main_view.KeepPositionInBothPanesWhenChangingContextSize, + main_view.KeepPositionInBothPanesWhenSwitchingDiffRenderers, main_view.KeepPositionWhenChangingContextSize, + main_view.KeepPositionWhenSwitchingDiffRenderers, main_view.KeepPositionWhenTheSelectionIsOffScreen, main_view.KeepScrollWhenTheDiffCantBeRead, main_view.KeepSelectionVisibleWhenDiffShrinks,