Open file diffs in the focused main view

Enter and double-click should take users to the diff where line actions now
live, whether the file belongs to the working tree or a commit. Share the
existing focus, raw-fallback, and selection setup while retaining directory,
submodule, and conflict-specific behavior.

Co-Authored-By: GitHub Copilot <copilot@github.com>
This commit is contained in:
Stefan Haller
2026-09-27 18:26:56 +02:00
co-authored by GitHub Copilot
parent 26abfa6c82
commit 7857f9995f
8 changed files with 104 additions and 53 deletions
@@ -557,40 +557,15 @@ func (self *CommitFilesController) currentFromToReverseForPatchBuilding() (strin
}
func (self *CommitFilesController) enter(node *filetree.CommitFileNode) error {
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
}
func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode, opts types.OnFocusOpts) error {
if node.File == nil {
return self.handleToggleCommitFileDirCollapsed(node)
}
if self.c.UserConfig().Git.DiffContextSize == 0 {
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextForCustomPatch,
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
}
return focusMainView(self.c, self.context(), -1)
}
from, to, reverse := self.currentFromToReverseForPatchBuilding()
mustDiscardPatch := self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.NewPatchRequired(from, to, reverse)
return self.c.ConfirmIf(mustDiscardPatch, types.ConfirmOpts{
Title: self.c.Tr.DiscardPatch,
Prompt: self.c.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
if mustDiscardPatch {
self.c.Git().Patch.PatchBuilder.Reset()
}
if !self.c.Git().Patch.PatchBuilder.Active() {
if err := self.startPatchBuilder(); err != nil {
return err
}
}
self.c.Context().Push(self.c.Contexts().CustomPatchBuilder, opts)
return nil
},
})
func (self *CommitFilesController) GetOnDoubleClick() func() error {
return self.withItemGraceful(self.enter)
}
func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *filetree.CommitFileNode) error {
+8 -10
View File
@@ -415,9 +415,7 @@ func (self *FilesController) GetFocusedMainViewDiffSource() types.FocusedMainVie
}
func (self *FilesController) GetOnDoubleClick() func() error {
return self.withItemGraceful(func(node *filetree.FileNode) error {
return self.press([]*filetree.FileNode{node})
})
return self.enter
}
// if we are dealing with a status for which there is no key in this map,
@@ -694,7 +692,7 @@ func (self *FilesController) getSelectedFile() *models.File {
}
func (self *FilesController) enter() error {
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
return self.enterFile(-1)
}
func (self *FilesController) collapseAll() error {
@@ -713,7 +711,10 @@ func (self *FilesController) expandAll() error {
return nil
}
func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
// enterFile focuses the diff of the selected file, which is where the commands that
// act on its lines live. clickedViewLineIdx is the row of the diff a click landed on,
// or -1 when the diff wasn't clicked.
func (self *FilesController) enterFile(clickedViewLineIdx int) error {
node := self.context().GetSelected()
if node == nil {
return nil
@@ -739,10 +740,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
return self.switchToMerge()
}
context := lo.Ternary(opts.ClickedWindowName == "secondary", self.c.Contexts().StagingSecondary, self.c.Contexts().Staging)
self.c.Context().Push(context, opts)
return nil
return focusMainView(self.c, self.context(), clickedViewLineIdx)
}
// conflictResolutionHint formats a conflict description for the main view,
@@ -1533,7 +1531,7 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
}
func (self *FilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y})
return self.enterFile(opts.Y)
}
func (self *FilesController) fetch() error {
@@ -71,20 +71,28 @@ func (self *SwitchToFocusedMainViewController) onClickSecondary(opts gocui.ViewM
}
func (self *SwitchToFocusedMainViewController) handleFocusMainView() error {
return focusMainView(self.c, self.context, -1)
}
func focusMainView(c *ControllerCommon, source types.Context, clickedLineIdx int) error {
// Usually the main pane, but the content can be in the secondary one alone: a file
// with nothing but staged changes shows them there.
mainViewContext := self.c.Contexts().Normal
if self.c.State().GetRepoState().GetMainPanes() == types.SecondaryPaneOnly {
mainViewContext = self.c.Contexts().NormalSecondary
mainViewContext := c.Contexts().Normal
if c.State().GetRepoState().GetMainPanes() == types.SecondaryPaneOnly {
mainViewContext = c.Contexts().NormalSecondary
}
return self.focusMainView(mainViewContext, -1)
return focusMainViewPane(c, source, mainViewContext, clickedLineIdx)
}
func (self *SwitchToFocusedMainViewController) focusMainView(mainViewContext *context.MainContext, clickedLineIdx int) error {
mainViewContext.ClearSearchString()
self.c.Context().Push(mainViewContext, types.OnFocusOpts{})
return focusMainViewPane(self.c, self.context, mainViewContext, clickedLineIdx)
}
if _, ok := self.context.(types.DiffMainViewContext); !ok {
func focusMainViewPane(c *ControllerCommon, source types.Context, mainViewContext *context.MainContext, clickedLineIdx int) error {
mainViewContext.ClearSearchString()
c.Context().Push(mainViewContext, types.OnFocusOpts{})
if _, ok := source.(types.DiffMainViewContext); !ok {
return nil
}
@@ -93,13 +101,13 @@ func (self *SwitchToFocusedMainViewController) focusMainView(mainViewContext *co
// is. Now that the user wants to act on it, it is re-rendered as git's own diff — the
// panel below decides that for itself, from the same question — and the selection
// goes on that instead of on rows we can't place.
if self.c.Helpers().DiffLine.MainViewDiffMode() == git_commands.DiffModeRaw {
self.c.Helpers().DiffLine.RenderFocusedMainViewAgain(mainViewContext.GetView(), self.context, func() {
self.c.Helpers().DiffLine.EstablishSelection(mainViewContext, clickedLineIdx)
if c.Helpers().DiffLine.MainViewDiffMode() == git_commands.DiffModeRaw {
c.Helpers().DiffLine.RenderFocusedMainViewAgain(mainViewContext.GetView(), source, func() {
c.Helpers().DiffLine.EstablishSelection(mainViewContext, clickedLineIdx)
})
return nil
}
self.c.Helpers().DiffLine.EstablishSelection(mainViewContext, clickedLineIdx)
c.Helpers().DiffLine.EstablishSelection(mainViewContext, clickedLineIdx)
return nil
}
@@ -131,7 +131,7 @@ var DiscardOldFileChanges = NewIntegrationTest(NewIntegrationTestArgs{
Equals(" A file2ToRemove"),
).
NavigateToLine(Contains("multiLineFile")).
Press(keys.Universal.FocusMainView)
PressEnter()
t.Views().Main().
IsFocused().
+1 -1
View File
@@ -52,7 +52,7 @@ var CustomPatch = NewIntegrationTest(NewIntegrationTestArgs{
IsFocused().
NavigateToLine(Contains("users.go")).
Wait(1000).
Press(keys.Universal.FocusMainView).
PressEnter().
Tap(func() {
t.Views().Main().
IsFocused().
+1 -1
View File
@@ -56,7 +56,7 @@ var StageLines = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
IsFocused().
Press(keys.Universal.FocusMainView)
PressEnter()
t.Views().Main().
IsFocused().
@@ -0,0 +1,69 @@
package main_view
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var EnterAndDoubleClickFocusFileDiff = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Enter and double-click focus a file's diff in the main view",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.UseHunkModeInStagingView = false
},
SetupRepo: func(shell *Shell) {
shell.CreateFileAndAdd("file1", "base\n")
shell.Commit("add file")
shell.UpdateFile("file1", "changed\n")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Contains("file1").IsSelected(),
).
PressEnter()
t.Views().Main().
IsFocused().
SelectedLines(Contains("-base")).
PressEscape()
t.Views().Files().
IsFocused().
Click(3, 0).
Click(3, 0)
t.Views().Main().
IsFocused().
SelectedLines(Contains("-base")).
PressEscape()
t.Views().Commits().
Focus().
Lines(
Contains("add file").IsSelected(),
).
PressEnter()
t.Views().CommitFiles().
IsFocused().
Lines(
Contains("file1").IsSelected(),
).
PressEnter()
t.Views().Main().
IsFocused().
SelectedLines(Contains("+base")).
PressEscape()
t.Views().CommitFiles().
IsFocused().
Click(3, 0).
Click(3, 0)
t.Views().Main().
IsFocused().
SelectedLines(Contains("+base"))
},
})
+1
View File
@@ -392,6 +392,7 @@ var tests = []*components.IntegrationTest{
main_view.EditHistoricalDiffLine,
main_view.EditHunkInFocusedDiff,
main_view.EditSelectedDiffLine,
main_view.EnterAndDoubleClickFocusFileDiff,
main_view.EscapeDismissesSelection,
main_view.FileNavigationScrollsToTheTop,
main_view.FocusFollowsAPaneEmptiedFromOutside,