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) <[email protected]>
This commit is contained in:
Stefan Haller
2026-10-10 13:44:43 +02:00
co-authored by Claude Opus 5.5
parent 2abe4848f3
commit 75083c179e
9 changed files with 220 additions and 1 deletions
+2
View File
@@ -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.
+1
View File
@@ -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"`
@@ -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 {
@@ -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) {
@@ -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)
},
})
@@ -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)
},
})
@@ -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)
},
})
+3
View File
@@ -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,
+1 -1
View File
@@ -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": {