Mark the lines of a commit's diff that are in the custom patch

A patch built from what is on screen has to show what is in it, over the
diff those lines were taken from — which may be a diff renderer's
rendering of it, whose bytes are none of ours to touch.

So the marks are drawn in a gutter over the content, from the lines the
patch holds rather than from where they were drawn last. They are worked
out again whenever a pane's content settles and whenever the patch
itself changes. Working them out as the content settles keeps them right
across a renderer switch, a change of context size, or a walk through
the commits.

They are shown whenever the main view shows the diff the patch is built
from, whether or not it has the focus. The pane beside the diff previews
the patch all the while, and marks that came and went with the focus
would look like a bug while browsing. A diff of any other commit gets
none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-10-04 19:01:02 +02:00
co-authored by Claude Opus 5
parent c38e85045a
commit d920db5129
16 changed files with 459 additions and 16 deletions
+31 -3
View File
@@ -893,13 +893,21 @@ func (v *View) SetInclusionGutter(show bool, marks []bool) {
}
}
// inclusionGutterWidth is how many columns the inclusion gutter takes while it is
// shown — the marker plus a column of space before the content — and 0 while it is
// not. Only call this with a lock on writeMutex.
// inclusionGutterWidth is how many columns the inclusion gutter takes from the content
// now: InclusionGutterWidthWhenShown while it is shown, and 0 while it is not. Only call
// this with a lock on writeMutex.
func (v *View) inclusionGutterWidth() int {
if !v.showInclusionGutter {
return 0
}
return v.InclusionGutterWidthWhenShown()
}
// InclusionGutterWidthWhenShown is how many columns the inclusion gutter takes from the
// content while it is shown, whether or not it is shown now: the marker plus a column of
// space before the content. Content laid out before the gutter appears has to be laid
// out this much narrower to fit beside it.
func (v *View) InclusionGutterWidthWhenShown() int {
return uniseg.StringWidth(v.InclusionGutterMarker) + 1
}
@@ -2031,6 +2039,26 @@ func (v *View) BufferLines() []string {
return lines
}
// MarkedLines returns the lines of the view's content that the inclusion gutter is
// marking (see SetInclusionGutter), in the order they appear. Empty while the gutter
// is hidden.
func (v *View) MarkedLines() []string {
v.writeMutex.Lock()
defer v.writeMutex.Unlock()
if !v.showInclusionGutter {
return nil
}
lines := []string{}
for i, line := range v.buf.lines {
if i < len(v.inclusionGutterMarks) && v.inclusionGutterMarks[i] {
lines = append(lines, line.cells.String())
}
}
return lines
}
// DiffLineContent holds what one line of a rendered diff offers to a reader trying
// to recover which line of which file it came from: the line's text, which can be
// parsed as a unified diff when the rendering preserves one, and the OSC 1717
@@ -5,6 +5,7 @@ import (
"path/filepath"
"strings"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/gocui"
@@ -104,6 +105,10 @@ func (self *CommitDiffActions) PrimaryAction(pane types.DiffPaneContext, firstBu
patchBuilder.Reset()
}
// The diff on screen is the one the marks belong to, so they can be brought up
// to date at once rather than waiting for the render below.
self.c.Helpers().DiffLine.RefreshInclusionGutter()
// The selection moves on past the lines just toggled, to the next change of
// the diff — which is still there, a toggle leaving the diff as it was, so
// hold input back until it has moved: a second press meanwhile would toggle
@@ -201,6 +206,37 @@ func (self *CommitDiffActions) DiscardSelectionDisabledReason(pane types.DiffPan
return nil
}
// PatchInclusion says which lines of the commit's diff are in the custom patch being
// built from it. nil when there is no such patch: none is being built at all, or the one
// being built is of another diff, whose lines are not these however alike they look.
func (self *CommitDiffActions) PatchInclusion() func(types.DiffLineInfo) bool {
patchBuilder := self.c.Git().Patch.PatchBuilder
target := self.target()
if !patchBuilder.Active() || target == nil {
return nil
}
from, reverse := self.patchEndpoints(target)
if patchBuilder.NewPatchRequired(from, target.to, reverse) {
return nil
}
// Which lines of a file are in the patch is asked of the patch builder per file, and
// a diff can span many, so each is asked about when a line of it first comes up.
includedByPath := map[string]*set.Set[patch.LineIdentity]{}
return func(info types.DiffLineInfo) bool {
path := self.patchBuilderPath(info.Path)
if path == "" {
return false
}
included, asked := includedByPath[path]
if !asked {
included = set.NewFromSlice(patchBuilder.IncludedLineIdentities(path))
includedByPath[path] = included
}
return included.Includes(info.PatchLineIdentity())
}
}
// togglePatchLines takes the given lines of the commit's diff into the custom patch, or
// out of it. The first line of the selection decides which of the two happens, once for
// the whole selection: pointing at a line that is already in the patch takes the whole
@@ -131,3 +131,66 @@ func (self *DiffLineHelper) SelectedHunkBounds(view *gocui.View) (int, int, bool
}
return self.ChangeBlockBounds(view, anchor)
}
// RefreshInclusionGutter updates the marks drawn over the diff in the main pane, which
// say which of its lines are in the custom patch being built from it.
//
// They are shown whenever the pane shows a diff the patch is being built from, whether
// or not it has the focus. The pane beside it previews the patch all the while, and the
// marks show the same lines from the side of the diff. A diff of some other commit gets
// none, since a patch built from another commit says nothing about the lines of this one.
//
// Call it whenever either of those can have changed: as a pane's content settles, and
// when the patch itself changes.
func (self *DiffLineHelper) RefreshInclusionGutter() {
view := self.c.Contexts().Normal.GetView()
included := self.gutterInclusion()
if included == nil {
view.SetInclusionGutter(false, nil)
return
}
resolved := self.resolveDiffLines(view.DiffLineContents())
marks := make([]bool, len(resolved))
for i, row := range resolved {
if row.ok && row.info.IsChange() {
marks[i] = included(row.info)
}
}
view.SetInclusionGutter(true, marks)
}
// ShowsInclusionGutter reports whether the custom patch's marks are drawn over what the
// given view is being given to show: the upper main pane, while it shows the diff of a
// panel the patch is being built from.
//
// It goes by what is known before the content arrives, so that a render can be laid out
// to the width the gutter leaves it. Whether the diff turns out to have any change lines
// to mark doesn't come into it.
func (self *DiffLineHelper) ShowsInclusionGutter(view *gocui.View) bool {
return view == self.c.Contexts().Normal.GetView() && self.gutterInclusion() != nil
}
// gutterInclusion is patchInclusion for the gutter over the upper main pane, which is
// shown only over the panel's diff and not, say, over a message in its place.
func (self *DiffLineHelper) gutterInclusion() func(types.DiffLineInfo) bool {
if !self.c.Contexts().Normal.ContentIsDiff() {
return nil
}
return self.patchInclusion()
}
// patchInclusion asks the panel whose diff the main pane is showing which of that diff's
// lines are in the custom patch being built from it, and answers nil where there is no
// such patch.
func (self *DiffLineHelper) patchInclusion() func(types.DiffLineInfo) bool {
// The side panel nearest the top of the stack, whether it holds the focus itself or
// either main pane above it does.
sidePanel := self.c.Context().CurrentSide()
actions, ok := sidePanel.GetFocusedMainViewDiffSource().(types.FocusedMainViewActions)
if !ok {
return nil
}
return actions.PatchInclusion()
}
@@ -188,6 +188,12 @@ func (self *WorkingTreeDiffActions) EditHunk(
return nil
}
// PatchInclusion is nil: a custom patch is built from a commit's diff, never from the
// working tree's, so no line of this diff is ever in one.
func (self *WorkingTreeDiffActions) PatchInclusion() func(types.DiffLineInfo) bool {
return nil
}
// diffLineSelection resolves what the user has selected in a pane of the focused main
// view to the change lines to act on, and reports whether they are the staged side of
// the diff — which is a question about the pane, so it is the same for every file of a
+12 -3
View File
@@ -379,8 +379,11 @@ func (gui *Gui) clearMainView(mainContext types.Context) {
}
}
// updateDiffSelectionVisibility works out whether a main pane holds anything for a
// selection to sit on, from what it is now showing: only beneath a panel whose main
// updateDiffPaneDecorations re-derives what is drawn over a main pane's content, rather
// than being part of it: whether a selection is shown, and which lines are marked as
// being in the custom patch.
//
// A pane holds something for a selection to sit on only beneath a panel whose main
// view is a diff, only while the pane is showing that diff rather than a message like
// "No changed files", and only while the diff holds something to select — never over
// one with nothing in it, such as a binary file's or an empty commit's. Whether the
@@ -392,7 +395,7 @@ func (gui *Gui) clearMainView(mainContext types.Context) {
// still being read can leave the question open (see diffPaneHasSomethingToSelect). The
// pane never answers from the render before it, and a render that leaves the question
// open is read on until it doesn't, so the answer is always about what is there.
func (gui *Gui) updateDiffSelectionVisibility(view *gocui.View, contentIsComplete bool) {
func (gui *Gui) updateDiffPaneDecorations(view *gocui.View, contentIsComplete bool) {
mainContext := gui.mainContextForView(view)
if mainContext == nil {
return
@@ -408,6 +411,12 @@ func (gui *Gui) updateDiffSelectionVisibility(view *gocui.View, contentIsComplet
} else {
gui.readOnUntilTheDiffPaneCanTell(view)
}
// The marks are over the diff in the upper pane; the lower one shows the patch
// they are marks of.
if view == gui.Views.Main {
gui.helpers.DiffLine.RefreshInclusionGutter()
}
}
// dropAnAnswerAboutAnotherRender takes away what the pane worked out about the content
+12 -1
View File
@@ -70,7 +70,7 @@ func (gui *Gui) newRenderTask(view *gocui.View, cmd *exec.Cmd, prefix string) er
gui.afterLayout(func() error {
// The layout may have changed the size of the view, so only now is the
// width to render at known, and with it the renderer command.
width := view.InnerWidth()
width := gui.renderWidth(view)
diffRendererConfigManager := gui.stateAccessor.GetDiffRendererConfigManager()
values := config.DiffRendererValues{
Width: width,
@@ -119,6 +119,17 @@ func (gui *Gui) newRenderTask(view *gocui.View, cmd *exec.Cmd, prefix string) er
return nil
}
// renderWidth is the width a render into view is laid out to: the view's own, less the
// columns the custom patch's marks take from it where they are drawn over the render
// (see DiffLineHelper.ShowsInclusionGutter).
func (gui *Gui) renderWidth(view *gocui.View) int {
width := view.InnerWidth()
if gui.helpers.DiffLine.ShowsInclusionGutter(view) {
width -= view.InclusionGutterWidthWhenShown()
}
return max(0, width)
}
// The start and onClose functions a render hands to its task: how to get the
// command running and the output reader for it, and how to tear it down again
// once the task is stopped.
+1 -2
View File
@@ -12,8 +12,7 @@ import (
)
func (gui *Gui) desiredPtySize(view *gocui.View) (cols, rows uint16) {
width, height := view.InnerSize()
return uint16(width), uint16(height)
return uint16(gui.renderWidth(view)), uint16(view.InnerHeight())
}
func (gui *Gui) onResize() error {
+7 -7
View File
@@ -33,7 +33,7 @@ func (gui *Gui) newCmdTask(view *gocui.View, cmd *exec.Cmd, prefix string) error
// 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()}
spec := renderSpec{view: view, cmd: cmd, width: gui.renderWidth(view)}
return gui.newTaskForRender(spec, prefix, cmdStr, gui.plainRender)
})
@@ -100,7 +100,7 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
f := func(tasks.TaskOpts) error {
return gui.g.OnUIThreadAndWaitBackground(func() {
gui.c.SetViewContent(view, str)
gui.updateDiffSelectionVisibility(view, true)
gui.updateDiffPaneDecorations(view, true)
gui.reApplySearch(view)
})
}
@@ -122,7 +122,7 @@ func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX in
return gui.g.OnUIThreadAndWaitBackground(func() {
gui.c.SetViewContent(view, str)
view.SetOrigin(originX, originY)
gui.updateDiffSelectionVisibility(view, true)
gui.updateDiffPaneDecorations(view, true)
gui.reApplySearch(view)
})
}
@@ -144,7 +144,7 @@ func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) e
return gui.g.OnUIThreadAndWaitBackground(func() {
gui.c.ResetViewOrigin(view)
gui.c.SetViewContent(view, str)
gui.updateDiffSelectionVisibility(view, true)
gui.updateDiffPaneDecorations(view, true)
gui.reApplySearch(view)
})
}
@@ -183,7 +183,7 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
// to say whether there is anything to select, and for a diff that
// opens with a long diffstat it isn't.
gui.c.OnUIThreadContentOnly(func() error {
gui.updateDiffSelectionVisibility(view, false)
gui.updateDiffPaneDecorations(view, false)
return nil
})
},
@@ -202,7 +202,7 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
view.SetOrigin(0, newOriginY)
}
gui.updateDiffSelectionVisibility(view, true)
gui.updateDiffPaneDecorations(view, true)
gui.clampDiffSelectionToContent(view)
gui.reApplySearch(view)
},
@@ -216,7 +216,7 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
// The content the pane is being given is on display from here on, so
// what is drawn over it is settled against that content rather than
// against the render before it.
gui.updateDiffSelectionVisibility(view, false)
gui.updateDiffPaneDecorations(view, false)
},
func() gocui.Task {
// A background task: rendering content into a view is display
+6
View File
@@ -265,6 +265,12 @@ type FocusedMainViewActions interface {
// is, and nil when it can. Taking lines out of a commit means rewriting it, which
// isn't always something we may do; the working tree has no such condition.
DiscardSelectionDisabledReason(pane DiffPaneContext) *DisabledReason
// PatchInclusion says which lines of the diff this panel shows are in the custom
// patch being built from it. The marks over those lines are drawn from this. nil
// where nothing about this diff is being built into a patch, which is always so
// for a diff that can't be.
PatchInclusion() func(info DiffLineInfo) bool
}
type IListContext interface {
+42
View File
@@ -437,6 +437,48 @@ func (self *ViewDriver) Content(matcher *TextMatcher) *ViewDriver {
return self
}
// MarkedLines asserts which lines of the view are marked as being in the custom patch
// being built. The marks are drawn over the content rather than being part of it, so
// they are read from the view rather than matched against what Content returns.
func (self *ViewDriver) MarkedLines(matchers ...*TextMatcher) *ViewDriver {
self.validateMatchersPassed(matchers)
self.t.assertWithRetries(func() (bool, string) {
markedLines := self.getView().MarkedLines()
markedContent := strings.Join(markedLines, "\n")
expectedContent := expectedContentFromMatchers(matchers)
if len(markedLines) != len(matchers) {
return false, fmt.Sprintf("%s: Expected the following lines to be marked as being in the custom patch:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", self.context, expectedContent, markedContent)
}
for i, line := range markedLines {
ok, message := matchers[i].test(line)
if !ok {
return false, fmt.Sprintf("%s: Error: %s. Expected the following lines to be marked as being in the custom patch:\n-----\n%s\n-----\nBut got:\n-----\n%s\n-----", self.context, message, expectedContent, markedContent)
}
}
return true, ""
})
return self
}
// NoMarkedLines asserts that no line of the view is marked as being in the custom
// patch, which is also what a view showing no marks at all reports.
func (self *ViewDriver) NoMarkedLines() *ViewDriver {
self.t.assertWithRetries(func() (bool, string) {
markedLines := self.getView().MarkedLines()
return len(markedLines) == 0, fmt.Sprintf(
"%s: Expected no line to be marked as being in the custom patch, but these were:\n-----\n%s\n-----",
self.context, strings.Join(markedLines, "\n"))
})
return self
}
// SelectionIsActive asserts that the view draws its selection as the one the user
// is working in. These three assertions read the highlight flags rather than the
// selected lines, which say nothing about whether the selection is drawn at all.
@@ -53,6 +53,10 @@ var BuildPatchFromACommitsDiff = NewIntegrationTest(NewIntegrationTestArgs{
Contains("-one"),
Contains(" two"),
)
// The line that is in the patch is marked as such over the diff itself.
t.Views().Main().MarkedLines(
Contains("-one"),
)
// The addition of the same modification goes in too, and the patch holds both.
t.Views().Main().
@@ -67,6 +71,10 @@ var BuildPatchFromACommitsDiff = NewIntegrationTest(NewIntegrationTestArgs{
Contains("+ONE"),
Contains(" two"),
)
t.Views().Main().MarkedLines(
Contains("-one"),
Contains("+ONE"),
)
// Pointing at a line that is in the patch takes it back out.
t.Views().Main().
@@ -80,6 +88,9 @@ var BuildPatchFromACommitsDiff = NewIntegrationTest(NewIntegrationTestArgs{
Contains(" two"),
).
Content(DoesNotContain("+ONE"))
t.Views().Main().MarkedLines(
Contains("-one"),
)
// Taking the last line out ends the patch, so the pane previewing it goes away.
t.Views().Main().
@@ -88,5 +99,6 @@ var BuildPatchFromACommitsDiff = NewIntegrationTest(NewIntegrationTestArgs{
PressPrimaryAction()
t.Views().Information().Content(DoesNotContain("Building patch"))
t.Views().Main().NoMarkedLines()
},
})
@@ -0,0 +1,43 @@
package main_view
import (
"strings"
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var MoveOnWhenThePatchMarksRewrapTheDiff = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Taking the first line into a custom patch moves the selection on to the next change, although the marks that come with the patch narrow the diff and wrap a line above the selection",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file", "one\ntwo\nthree\nfour\nfive\n")
shell.Commit("first commit")
shell.UpdateFileAndAdd("file", "one\nTWO\nthree\nFOUR\nfive\n")
// git draws the diffstat line of a file with this many changes as wide as the
// view, so the columns the marks take make that line wrap.
shell.CreateFileAndAdd("many", strings.Repeat("line\n", 300))
shell.Commit("second commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
NavigateToLine(Contains("+TWO")).
PressPrimaryAction().
MarkedLines(
Contains("+TWO"),
).
SelectedLines(
Contains("-four"),
)
},
})
@@ -0,0 +1,58 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var PatchMarksFollowARendererSwitch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Switching diff renderers mid-build leaves the marks on the lines that are in the custom patch, wherever the new rendering puts them",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.UseHunkModeInStagingView = false
// Two renderers that announce the metadata protocol — so that focusing the main
// view keeps their output rather than falling back to git's own — and pass the
// diff through under a banner of their own. The second one's banner is a line
// longer, so every line of the diff it renders is a line further down than the
// first one's.
cfg.GetUserConfig().Git.DiffRenderers = []config.DiffRendererConfig{
{Name: "one", Command: `printf '\033]1717;1\007RENDERED BY ONE\n'; cat`},
{Name: "two", Command: `printf '\033]1717;1\007RENDERED BY TWO\nAND ONE MORE LINE\n'; cat`},
}
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
shell.Commit("first commit")
shell.UpdateFileAndAdd("file1", "one\nTWO\nthree\n")
shell.Commit("second commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
Content(Contains("RENDERED BY ONE")).
SelectedLines(
Contains("-two"),
).
PressPrimaryAction().
MarkedLines(
Contains("-two"),
).
Press(keys.Universal.CycleDiffRenderers)
t.ExpectToast(Equals("Diff renderer: two (2 of 2)"))
// The marks are of lines of the diff, not of rows of the rendering, so the new
// rendering has them on the same line of the file.
t.Views().Main().
Content(Contains("AND ONE MORE LINE")).
MarkedLines(
Contains("-two"),
)
},
})
@@ -0,0 +1,69 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var PatchMarksShowWheneverTheirDiffIsOnScreen = NewIntegrationTest(NewIntegrationTestArgs{
Description: "The marks over the lines in the custom patch are shown whenever the main view shows the diff the patch is built from, whether or not it has the focus, and not over the diff of another commit",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
shell.Commit("first commit")
shell.UpdateFileAndAdd("file1", "one\nTWO\nthree\n")
shell.Commit("second commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
SelectedLines(
Contains("-two"),
).
PressPrimaryAction().
MarkedLines(
Contains("-two"),
).
Press(keys.Universal.TogglePanel)
t.Views().Secondary().IsFocused()
t.Views().Main().MarkedLines(
Contains("-two"),
)
// Leaving the main view keeps them, as it keeps the patch previewed beside the
// diff.
t.Views().Secondary().Press(keys.Universal.Return)
t.Views().Commits().IsFocused()
t.Views().Main().MarkedLines(
Contains("-two"),
)
// The diff of another commit has none of the patch's lines.
t.Views().Commits().
Lines(
Contains("second commit").IsSelected(),
Contains("first commit"),
).
SelectNextItem()
t.Views().Main().NoMarkedLines()
// And they are back with the diff they belong to.
t.Views().Commits().SelectPreviousItem()
t.Views().Main().MarkedLines(
Contains("-two"),
)
},
})
@@ -0,0 +1,57 @@
package main_view
import (
"strings"
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var RenderTheDiffBesideThePatchMarks = NewIntegrationTest(NewIntegrationTestArgs{
Description: "A diff that the custom patch's marks are shown over is rendered to the width they leave it, so that a line as wide as the view still fits beside them, whether or not the view has the focus",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file", "one\ntwo\nthree\n")
shell.Commit("first commit")
shell.UpdateFileAndAdd("file", "one\nTWO\nthree\n")
// git draws the diffstat line of a file with this many changes as wide as it is
// told the view is.
shell.CreateFileAndAdd("many", strings.Repeat("line\n", 300))
shell.Commit("second commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Press(keys.Universal.FocusMainView)
t.Views().Main().
IsFocused().
PressPrimaryAction().
MarkedLines(
Contains("-two"),
Contains("+TWO"),
).
ContainsViewLines(
Contains("many | 300"),
Contains("2 files changed"),
)
// The commits panel renders the diff again as it takes the focus back, and the
// marks are still shown over it.
t.Views().Main().Press(keys.Universal.Return)
t.Views().Commits().IsFocused()
t.Views().Main().
MarkedLines(
Contains("-two"),
Contains("+TWO"),
).
ContainsViewLines(
Contains("many | 300"),
Contains("2 files changed"),
)
},
})
+4
View File
@@ -405,13 +405,17 @@ var tests = []*components.IntegrationTest{
main_view.KeepScrollWhenTheDiffCantBeRead,
main_view.KeepSelectedRangeWhenChangingContextSize,
main_view.KeepSelectionVisibleWhenDiffShrinks,
main_view.MoveOnWhenThePatchMarksRewrapTheDiff,
main_view.NavigateByHunkAndFile,
main_view.NoSelectionOverABinaryDiff,
main_view.NoSelectionOverACommitLog,
main_view.NoSelectionOverAConflictHint,
main_view.NoSelectionWhenNoChanges,
main_view.PatchMarksFollowARendererSwitch,
main_view.PatchMarksShowWheneverTheirDiffIsOnScreen,
main_view.RangeSelectDiffLines,
main_view.RawFallbackUnderAnExternalDiff,
main_view.RenderTheDiffBesideThePatchMarks,
main_view.ResetAPatchBuiltFromACommitsDiff,
main_view.ResetThePatchFromThePaneShowingIt,
main_view.SearchCollapsesTheSelection,