From 75083c179e78cf391827240811969fae19c4e965 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 9 Oct 2026 08:53:56 +0200 Subject: [PATCH] Scroll the focused diff sideways with H and L If gui.wrapLinesInDiffView is false, long lines of a diff run off the right edge of the main view, and there is no way to see the rest of them. This makes it hard to see what has changed in such a line. The old staging and patch-building views scrolled sideways with H and L, but the focused main view that replaced them has no binding for these keys. Bind them in the focused main view, as in the side panels. Leaving the view scrolls it back to the left edge, as it did for the staging view. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs-master/Config.md | 2 + pkg/config/user_config.go | 1 + pkg/gui/controllers/main_view_controller.go | 12 +++ pkg/integration/components/view_driver.go | 9 +++ ...o_another_file_scrolls_to_the_left_edge.go | 64 ++++++++++++++++ ...scroll_back_when_the_diff_gets_narrower.go | 53 +++++++++++++ .../main_view/scroll_diff_horizontally.go | 75 +++++++++++++++++++ pkg/integration/tests/test_list.go | 3 + schema-master/config.json | 2 +- 9 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 pkg/integration/tests/main_view/jump_to_another_file_scrolls_to_the_left_edge.go create mode 100644 pkg/integration/tests/main_view/scroll_back_when_the_diff_gets_narrower.go create mode 100644 pkg/integration/tests/main_view/scroll_diff_horizontally.go diff --git a/docs-master/Config.md b/docs-master/Config.md index d35455c84..c8bb8c5f6 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -145,6 +145,8 @@ gui: # If true, wrap lines in focused diffs to the width of the view. This makes it # much easier to work with diffs that have long lines, e.g. paragraphs of # markdown text. + # If false, scroll a focused diff sideways with the scrollLeft and scrollRight + # keys (H and L by default). wrapLinesInDiffView: true # If true, hunk selection mode will be enabled by default when focusing a diff. diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index eedde41e0..140defcf3 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -123,6 +123,7 @@ type GuiConfig struct { // - 'top': split the window vertically (side panel on top, main view below) EnlargedSideViewLocation string `yaml:"enlargedSideViewLocation"` // If true, wrap lines in focused diffs to the width of the view. This makes it much easier to work with diffs that have long lines, e.g. paragraphs of markdown text. + // If false, scroll a focused diff sideways with the scrollLeft and scrollRight keys (H and L by default). WrapLinesInDiffView bool `yaml:"wrapLinesInDiffView"` // If true, hunk selection mode will be enabled by default when focusing a diff. UseHunkModeInDiffView bool `yaml:"useHunkModeInDiffView"` diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index 7129e1c43..9135f363f 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -238,6 +238,8 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty {Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.NextPage), Handler: self.handleNextPage, Description: self.c.Tr.NextPage}, {Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.GotoTop), Handler: self.handleGotoTop, Description: self.c.Tr.GotoTop}, {Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.GotoBottom), Handler: self.handleGotoBottom, Description: self.c.Tr.GotoBottom}, + {Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.ScrollLeft), Handler: self.handleScrollLeft, Description: self.c.Tr.ScrollLeft}, + {Tag: "navigation", Keys: opts.GetKeys(opts.Config.Universal.ScrollRight), Handler: self.handleScrollRight, Description: self.c.Tr.ScrollRight}, } } @@ -1052,6 +1054,16 @@ func (self *MainViewController) handleGotoBottom() error { return nil } +func (self *MainViewController) handleScrollLeft() error { + self.context.GetViewTrait().ScrollLeft() + return nil +} + +func (self *MainViewController) handleScrollRight() error { + self.context.GetViewTrait().ScrollRight() + return nil +} + func (self *MainViewController) editLine() error { view := self.context.GetView() if !view.Highlight { diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index b5168487a..d1b65a0d9 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -568,6 +568,15 @@ func (self *ViewDriver) OriginY(expected int) *ViewDriver { return self } +func (self *ViewDriver) OriginX(expected int) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + actual := self.getView().OriginX() + return expected == actual, fmt.Sprintf("%s: Expected origin X to be %d, got %d", self.context, expected, actual) + }) + + return self +} + // asserts that the selected line is inside the visible area of the view func (self *ViewDriver) SelectedLineIsVisible() *ViewDriver { self.t.assertWithRetries(func() (bool, string) { diff --git a/pkg/integration/tests/main_view/jump_to_another_file_scrolls_to_the_left_edge.go b/pkg/integration/tests/main_view/jump_to_another_file_scrolls_to_the_left_edge.go new file mode 100644 index 000000000..507668d73 --- /dev/null +++ b/pkg/integration/tests/main_view/jump_to_another_file_scrolls_to_the_left_edge.go @@ -0,0 +1,64 @@ +package main_view + +import ( + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var JumpToAnotherFileScrollsToTheLeftEdge = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Jumping to another file of a diff scrolled sideways shows it from the left edge, while jumping to another hunk doesn't", + ExtraCmdArgs: []string{}, + Skip: false, + Width: 120, + Height: 30, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.WrapLinesInDiffView = false + cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false + }, + SetupRepo: func(shell *Shell) { + long := strings.Repeat("word ", 60) + shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n") + shell.CreateFileAndAdd("file2", "one\ntwo\nthree\n") + shell.Commit("one") + + shell.UpdateFileAndAdd("file1", "one\ntwo\nthree "+long+"\nfour\nfive\nsix\nseven\neight\nnine "+long+"\nten\n") + shell.UpdateFileAndAdd("file2", "one\ntwo\nthree "+long+"\n") + shell.Commit("two") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + Focus(). + Lines( + Contains("two").IsSelected(), + Contains("one"), + ). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + SelectedLines( + Contains("-three"), + ). + Press(keys.Universal.ScrollRight). + OriginX(26). + Press(keys.Main.NextHunk). + SelectedLines( + Contains("-nine"), + ). + OriginX(26). + Press(keys.Main.NextFile). + SelectedLines( + Contains("diff --git a/file2 b/file2"), + ). + OriginX(0). + Press(keys.Universal.ScrollRight). + OriginX(26). + Press(keys.Main.PrevFile). + SelectedLines( + Contains("diff --git a/file1 b/file1"), + ). + OriginX(0) + }, +}) diff --git a/pkg/integration/tests/main_view/scroll_back_when_the_diff_gets_narrower.go b/pkg/integration/tests/main_view/scroll_back_when_the_diff_gets_narrower.go new file mode 100644 index 000000000..57c301ddc --- /dev/null +++ b/pkg/integration/tests/main_view/scroll_back_when_the_diff_gets_narrower.go @@ -0,0 +1,53 @@ +package main_view + +import ( + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ScrollBackWhenTheDiffGetsNarrower = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "A diff scrolled sideways scrolls back when it is rendered again narrower than where it was scrolled to", + ExtraCmdArgs: []string{}, + Skip: false, + Width: 120, + Height: 30, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.WrapLinesInDiffView = false + cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\n") + shell.Commit("one") + + shell.UpdateFile("file1", "ONE\ntwo\nthree\nfour\nfive "+strings.Repeat("word ", 60)+"\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + Press(keys.Main.NextHunk). + SelectedLines( + Contains("-five"), + ). + Press(keys.Universal.RangeSelectDown). + SelectedLines( + Contains("-five"), + Contains("+five word"), + ). + Press(keys.Universal.ScrollRight). + Press(keys.Universal.ScrollRight). + OriginX(52). + // What is left of the diff fits in the pane, so the pane goes back to the left + // edge. + PressPrimaryAction(). + SelectedLines( + Contains("+ONE"), + ). + OriginX(0) + }, +}) diff --git a/pkg/integration/tests/main_view/scroll_diff_horizontally.go b/pkg/integration/tests/main_view/scroll_diff_horizontally.go new file mode 100644 index 000000000..ba9f29a5b --- /dev/null +++ b/pkg/integration/tests/main_view/scroll_diff_horizontally.go @@ -0,0 +1,75 @@ +package main_view + +import ( + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var ScrollDiffHorizontally = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "With wrapping turned off for diffs, the focused diff scrolls sideways, and stays scrolled as the selection moves and the diff is rendered again", + ExtraCmdArgs: []string{}, + Skip: false, + Width: 120, + Height: 30, + SetupConfig: func(cfg *config.AppConfig) { + cfg.GetUserConfig().Gui.WrapLinesInDiffView = false + cfg.GetUserConfig().Gui.UseHunkModeInDiffView = false + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file1", "one\ntwo\n") + shell.Commit("one") + + long := strings.Repeat("word ", 60) + shell.UpdateFile("file1", "one "+long+"\ntwo "+long+"\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + SelectedLines( + Contains("-one"), + ). + OriginX(0). + // A third of the pane's width at a time. + Press(keys.Universal.ScrollRight). + OriginX(26). + Press(keys.Universal.ScrollLeft). + OriginX(0). + Press(keys.Universal.ScrollLeft). + OriginX(0). + Press(keys.Universal.ScrollRight). + Press(keys.Universal.ScrollRight). + OriginX(52). + SelectNextItem(). + SelectedLines( + Contains("-two"), + ). + OriginX(52). + Press(keys.Universal.IncreaseContextInDiffView). + Tap(func() { + t.ExpectToast(Equals("Changed diff context size to 4")) + }). + SelectedLines( + Contains("-two"), + ). + OriginX(52). + PressPrimaryAction(). + SelectedLines( + Contains("+one"), + ). + OriginX(52). + // Leaving the view gives up the horizontal scroll position. + PressEscape() + + t.Views().Files(). + IsFocused() + + t.Views().Main(). + OriginX(0) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 566baf0dc..2381fa1a2 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -412,6 +412,7 @@ var tests = []*components.IntegrationTest{ main_view.JumpToAFileOfAStashWithNoPrefixes, main_view.JumpToAFileOfTheDiff, main_view.JumpToAFileOnlyOverADiff, + main_view.JumpToAnotherFileScrollsToTheLeftEdge, main_view.KeepAWrappedLineCoveredAcrossARerender, main_view.KeepAnEmptiedPaneEmpty, main_view.KeepBothHalvesOfAChangeSelected, @@ -470,6 +471,8 @@ var tests = []*components.IntegrationTest{ main_view.ResetAPatchBuiltFromACommitsDiff, main_view.ResetPatchWithEscape, main_view.ResetThePatchFromThePaneShowingIt, + main_view.ScrollBackWhenTheDiffGetsNarrower, + main_view.ScrollDiffHorizontally, main_view.SearchCollapsesTheSelection, main_view.SearchFollowsTheSelection, main_view.SelectBelowALongCommitMessage, diff --git a/schema-master/config.json b/schema-master/config.json index 2607715b2..869b8fb60 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -671,7 +671,7 @@ }, "wrapLinesInDiffView": { "type": "boolean", - "description": "If true, wrap lines in focused diffs to the width of the view. This makes it much easier to work with diffs that have long lines, e.g. paragraphs of markdown text.", + "description": "If true, wrap lines in focused diffs to the width of the view. This makes it much easier to work with diffs that have long lines, e.g. paragraphs of markdown text.\nIf false, scroll a focused diff sideways with the scrollLeft and scrollRight keys (H and L by default).", "default": true }, "useHunkModeInDiffView": {