Fix drawing git's diff stat after switching screen modes (#6087)

Changing the screen mode renders the main view again, and so does
anything else that changes its size along with what it shows. With git's
own diff, that render laid a commit's diffstat out to the width the view
had before. The graph then wrapped onto rows of its own in a view that
got narrower, and stopped short in one that got wider.

A diff renderer's render is created after the layout pass, since only
the layout settles the view's size, and takes its width there. git's own
diff took its width straight away instead, when the render was asked
for. Create its task after the layout as well.

This bug is going to be a little more prominent with the upcoming PR
series that moves hunk staging to the main view; in that case, when you
stage the first hunk into a custom patch, the main view can get split
side-by-side, and without this fix the left half of the main view would
render its diff stat to the old width of the view.
This commit is contained in:
Stefan Haller
2026-10-04 15:42:37 +02:00
committed by GitHub
4 changed files with 99 additions and 4 deletions
+10 -4
View File
@@ -27,11 +27,17 @@ func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
// thumb to shrink and snap back as the rest loads.
view.FreezeScrollbarHeight()
// Snapshot the view width here, on the UI thread, so the task goroutine
// doesn't read the view's live dimensions while it streams output.
spec := renderSpec{view: view, cmd: cmd, width: view.InnerWidth()}
// The command lays its output out to the width of the view (git's diffstat
// graph does), and only the layout settles that, so the task is created after
// it, as a diff renderer's is. Taking the width there, on the UI thread, also
// keeps the task goroutine from reading the view's live dimensions while it
// streams output.
gui.afterLayout(func() error {
spec := renderSpec{view: view, cmd: cmd, width: view.InnerWidth()}
return gui.newTaskForRender(spec, prefix, cmdStr, gui.plainRender)
})
return gui.newTaskForRender(spec, prefix, cmdStr, gui.plainRender)
return nil
}
// plainRender runs the command as it is, with its output going straight into
+33
View File
@@ -141,6 +141,39 @@ func (self *ViewDriver) VisibleLines(matchers ...*TextMatcher) *ViewDriver {
return self.assertLines(originY, matchers...)
}
// ContainsViewLines asserts that somewhere in the view there are consecutive view lines
// matching the given matchers. View lines are the rows the view draws its content in,
// so a line too long for the view is as many view lines as the view wraps it into.
func (self *ViewDriver) ContainsViewLines(matchers ...*TextMatcher) *ViewDriver {
self.validateMatchersPassed(matchers)
self.t.assertWithRetries(func() (bool, string) {
lines := self.getView().ViewBufferLines()
for i := 0; i+len(matchers) <= len(lines); i++ {
matches := true
for j, matcher := range matchers {
if ok, _ := matcher.test(lines[i+j]); !ok {
matches = false
break
}
}
if matches {
return true, ""
}
}
return false, fmt.Sprintf(
"%s: Expected the following view lines to be contained:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----",
self.context,
expectedContentFromMatchers(matchers),
strings.Join(lines, "\n"),
)
})
return self
}
// asserts that somewhere in the view there are consecutive lines matching the given matchers.
func (self *ViewDriver) ContainsLines(matchers ...*TextMatcher) *ViewDriver {
self.validateMatchersPassed(matchers)
@@ -0,0 +1,55 @@
package diff
import (
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var StatFollowsAScreenModeChange = NewIntegrationTest(NewIntegrationTestArgs{
Description: "A commit's diffstat is laid out again to the width the view has after a screen mode change narrows it",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
// git's own diff, so that the render runs as a plain command rather
// than through a renderer.
cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{
{Type: "rawGit"},
}
},
SetupRepo: func(shell *Shell) {
// Enough added lines that git scales the graph to the width it has,
// rather than drawing one mark per line.
lines := make([]string, 200)
for i := range lines {
lines[i] = "line " + strconv.Itoa(i)
}
shell.CreateFileAndAdd("file1", strings.Join(lines, "\n")+"\n")
shell.Commit("add file1")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("add file1").IsSelected(),
)
t.Views().Main().
ContainsViewLines(
Contains("file1 | 200"),
Contains("1 file changed"),
)
// The focused panel takes half the screen, which leaves the main view
// narrower than it was.
t.Views().Commits().Press(keys.Universal.NextScreenMode)
t.Views().Main().
ContainsViewLines(
Contains("file1 | 200"),
Contains("1 file changed"),
)
},
})
+1
View File
@@ -231,6 +231,7 @@ var tests = []*components.IntegrationTest{
diff.IgnoreWhitespace,
diff.RenameSimilarityThresholdChange,
diff.RenderThroughAPipe,
diff.StatFollowsAScreenModeChange,
diff.StatUsesTheViewWidth,
file.ClickArrowToCollapse,
file.CollapseExpand,