mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 23:56:24 -04:00
Push the staging focus-follow into the side-panel handler
The focused main view's space binding delegates to the side panel beneath it, but the two handlers were asymmetric: the patch-toggle handler did its own re-render and re-established the selection itself, while the staging handler returned a focusViewName so the dispatcher (stageRange) could do the reveal-and-focus dance on its behalf. That split blocks collapsing the per-command handler channels into one, since the two have different return types. Make staging match the toggle: GetOnStageFocusedMainView returns plain error, and the FilesController handler does the post-staging reveal and pane focus itself. The reveal/select-mode logic the two handlers shared (collapse a range to a line, preserve the change-line ordinal across the re-render, re-expand a hunk) is extracted into revealSelectionAfterPrimaryAction, with mainContextForViewName resolving a main view name to its context. The dispatcher is now uniform: read the selected range, hand it to whichever handler the panel registered. Behavior-preserving. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b02020022
commit
96b09bb41d
|
|
@ -39,7 +39,7 @@ type (
|
|||
onFocusFn = func(types.OnFocusOpts)
|
||||
onFocusLostFn = func(types.OnFocusLostOpts)
|
||||
onClickFocusedMainViewFn = func(mainViewName string, clickedLineIdx int) error
|
||||
onStageFocusedMainViewFn = func(mainViewName string, firstLineIdx int, lastLineIdx int) (focusViewName string, err error)
|
||||
onStageFocusedMainViewFn = func(mainViewName string, firstLineIdx int, lastLineIdx int) error
|
||||
onTogglePatchFocusedMainViewFn = func(mainViewName string, firstLineIdx int, lastLineIdx int) error
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func (self *baseController) GetOnClickFocusedMainView() func(mainViewName string
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnStageFocusedMainView() func(mainViewName string, firstLineIdx int, lastLineIdx int) (focusViewName string, err error) {
|
||||
func (self *baseController) GetOnStageFocusedMainView() func(mainViewName string, firstLineIdx int, lastLineIdx int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -457,21 +457,21 @@ func (self *FilesController) diffSplitState(node *filetree.FileNode) (split bool
|
|||
return split, mainShowsStaged
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnStageFocusedMainView() func(mainViewName string, firstLineIdx int, lastLineIdx int) (string, error) {
|
||||
return func(mainViewName string, firstLineIdx int, lastLineIdx int) (string, error) {
|
||||
func (self *FilesController) GetOnStageFocusedMainView() func(mainViewName string, firstLineIdx int, lastLineIdx int) error {
|
||||
return func(mainViewName string, firstLineIdx int, lastLineIdx int) error {
|
||||
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
||||
return "", fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToStage,
|
||||
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToStage,
|
||||
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
||||
}
|
||||
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
infos := self.c.Helpers().Staging.ChangeLinesInViewRange(mainViewName, firstLineIdx, lastLineIdx)
|
||||
if len(infos) == 0 {
|
||||
return "", nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// The whole diff shown in the main view is on one side — the staged diff in
|
||||
|
|
@ -492,7 +492,7 @@ func (self *FilesController) GetOnStageFocusedMainView() func(mainViewName strin
|
|||
continue
|
||||
}
|
||||
if err := self.stageDiffLines(file, fileInfos, reverse); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -513,7 +513,15 @@ func (self *FilesController) GetOnStageFocusedMainView() func(mainViewName strin
|
|||
}
|
||||
}
|
||||
}
|
||||
return focusViewName, nil
|
||||
|
||||
// The staging Refresh above queued the main-view re-render; re-establish the
|
||||
// selection in whichever pane now holds the acted-on side once that render lands,
|
||||
// and focus that pane if staging moved it there.
|
||||
revealSelectionAfterPrimaryAction(self.c, mainViewName, focusViewName, firstLineIdx)
|
||||
if focusViewName != mainViewName {
|
||||
self.c.Context().Push(mainContextForViewName(self.c, focusViewName), types.OnFocusOpts{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -280,80 +280,59 @@ func (self *MainViewController) isDiffView() bool {
|
|||
}
|
||||
|
||||
// stageSelectedLine acts on the selected diff line(s) — a single line, a range, or a
|
||||
// hunk — delegating to the side panel beneath the focused main view, since what the
|
||||
// action means is the panel's business: the working tree stages, while commits build a
|
||||
// custom patch. Panels whose diff supports neither register no handler, so this is a
|
||||
// no-op there.
|
||||
// hunk — delegating the primary action to the side panel beneath the focused main view,
|
||||
// since what the action means is the panel's business: the working tree stages, while
|
||||
// commits toggle the selection into a custom patch. Each handler does its own re-render
|
||||
// and re-establishes the selection afterwards (see revealSelectionAfterPrimaryAction), so
|
||||
// the dispatcher just hands over the selected range. Panels whose diff supports neither
|
||||
// register no handler, so this is a no-op there.
|
||||
func (self *MainViewController) stageSelectedLine() error {
|
||||
sidePanelContext := self.c.Context().NextInStack(self.context)
|
||||
if sidePanelContext == nil {
|
||||
return nil
|
||||
}
|
||||
v := self.context.GetView()
|
||||
first, last := v.SelectedLineRange()
|
||||
if handler := sidePanelContext.GetOnStageFocusedMainView(); handler != nil {
|
||||
return self.stageRange(handler)
|
||||
return handler(self.context.GetViewName(), first, last)
|
||||
}
|
||||
if handler := sidePanelContext.GetOnTogglePatchFocusedMainView(); handler != nil {
|
||||
// Toggling the selection into the custom patch leaves the diff unchanged (the
|
||||
// handler repaints the inclusion gutter itself), so unlike staging there's no
|
||||
// re-render to ride and the selection stays where it is.
|
||||
v := self.context.GetView()
|
||||
first, last := v.SelectedLineRange()
|
||||
return handler(self.context.GetViewName(), first, last)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// stageRange stages (or unstages) the current selection through the side panel's
|
||||
// staging handler. Staging mutates the working tree, so the diff re-renders
|
||||
// asynchronously and the selection is re-revealed once it lands.
|
||||
func (self *MainViewController) stageRange(handler func(mainViewName string, firstLineIdx int, lastLineIdx int) (string, error)) error {
|
||||
v := self.context.GetView()
|
||||
first, last := v.SelectedLineRange()
|
||||
// revealSelectionAfterPrimaryAction re-establishes the focused-main-view selection after a
|
||||
// primary action (staging or a patch toggle) re-renders the diff. The selection's
|
||||
// change-line ordinal is read from the source pane (still showing the pre-action diff
|
||||
// until the queued re-render) and re-applied once the target pane re-renders — so the
|
||||
// selection lands on the change nearest the one acted on rather than at a stale position.
|
||||
// sourceViewName and targetViewName are usually the same pane, but staging can move the
|
||||
// acted-on side to the other pane (passing that pane as the target). The target inherits
|
||||
// the (collapsed) select mode; a range collapses back to a single line, hunk mode stays
|
||||
// on to land on the next hunk.
|
||||
func revealSelectionAfterPrimaryAction(c *ControllerCommon, sourceViewName string, targetViewName string, firstLineIdx int) {
|
||||
sourceContext := mainContextForViewName(c, sourceViewName)
|
||||
targetContext := mainContextForViewName(c, targetViewName)
|
||||
|
||||
// Staging consumes the selected range, so a range selection collapses back to a
|
||||
// single line; hunk mode stays on, to land on the next hunk.
|
||||
sel := self.sel()
|
||||
sel := sourceContext.DiffSelectState()
|
||||
if sel.Mode == context.DiffSelectModeRange {
|
||||
sel.Mode = context.DiffSelectModeLine
|
||||
sel.RangeIsSticky = false
|
||||
}
|
||||
|
||||
// Staging updates the model synchronously and queues the main-view re-render, and
|
||||
// reports which pane should hold focus afterwards (staging/unstaging can move the
|
||||
// acted-on side to the other pane). "" means nothing was staged.
|
||||
focusViewName, err := handler(self.context.GetViewName(), first, last)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if focusViewName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Re-select in whichever pane now holds the acted-on side, and focus it. The
|
||||
// acted-on line's change-line ordinal is read from the pane we acted in (its
|
||||
// content is still the pre-staging diff until the queued re-render) and re-applied
|
||||
// in the target pane's re-render. The target inherits our select mode (line/hunk).
|
||||
targetContext := self.context
|
||||
if focusViewName == self.otherContext.GetViewName() {
|
||||
targetContext = self.otherContext
|
||||
}
|
||||
*targetContext.DiffSelectState() = *sel
|
||||
targetView := targetContext.GetView()
|
||||
mode := sel.Mode
|
||||
|
||||
self.c.Helpers().Staging.RevealSelectionAfterStaging(v, targetView, first, func(viewLine int) {
|
||||
if sel.Mode == context.DiffSelectModeHunk {
|
||||
selectDiffHunk(self.c, targetContext, viewLine)
|
||||
sourceView := sourceContext.GetView()
|
||||
targetView := targetContext.GetView()
|
||||
c.Helpers().Staging.RevealSelectionAfterStaging(sourceView, targetView, firstLineIdx, func(viewLine int) {
|
||||
if mode == context.DiffSelectModeHunk {
|
||||
selectDiffHunk(c, targetContext, viewLine)
|
||||
} else {
|
||||
targetView.CancelRangeSelect()
|
||||
showSelectionAtLine(targetView, viewLine, true)
|
||||
}
|
||||
})
|
||||
|
||||
if targetContext != self.context {
|
||||
self.c.Context().Push(targetContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MainViewController) enter() error {
|
||||
|
|
@ -674,15 +653,22 @@ func sidePanelShowsDiff(sidePanel types.Context) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// focusedMainViewContextForViewName maps a focused main view's view name (as
|
||||
// passed to GetOnClickFocusedMainView) to its context.
|
||||
func focusedMainViewContextForViewName(c *ControllerCommon, viewName string) types.Context {
|
||||
// mainContextForViewName maps a focused main view's view name (as passed to the
|
||||
// side-panel handlers) to its main context — the secondary pane for the secondary
|
||||
// view name, the primary pane otherwise.
|
||||
func mainContextForViewName(c *ControllerCommon, viewName string) *context.MainContext {
|
||||
if viewName == c.Contexts().NormalSecondary.GetViewName() {
|
||||
return c.Contexts().NormalSecondary
|
||||
}
|
||||
return c.Contexts().Normal
|
||||
}
|
||||
|
||||
// focusedMainViewContextForViewName is mainContextForViewName as a types.Context, for
|
||||
// callers that only need the interface.
|
||||
func focusedMainViewContextForViewName(c *ControllerCommon, viewName string) types.Context {
|
||||
return mainContextForViewName(c, viewName)
|
||||
}
|
||||
|
||||
// focusedMainViewSnapshot records the focused main view to return to when diving
|
||||
// into a patch explorer from it, so escaping can come back with the main view
|
||||
// focused. sidePanel is the panel to land on first (which re-renders the
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
|
@ -59,7 +58,10 @@ func togglePatchFromFocusedMainView(
|
|||
}
|
||||
|
||||
refresh()
|
||||
revealSelectionAfterPatchToggle(c, mainViewName, firstLineIdx)
|
||||
// A toggle doesn't change the diff, so source and target are the same pane;
|
||||
// the re-render (and the layout re-wrap when the secondary view first appears)
|
||||
// still moves the selection in view-line space, so re-establish it.
|
||||
revealSelectionAfterPrimaryAction(c, mainViewName, mainViewName, firstLineIdx)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
|
@ -126,37 +128,6 @@ func togglePatchLines(c *ControllerCommon, infos []types.DiffLineInfo) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// revealSelectionAfterPatchToggle re-establishes the selection after a toggle's
|
||||
// re-render. Toggling doesn't change the diff, so the same content is still there — but
|
||||
// the re-render (and the layout re-wrap when the secondary view first appears) is in
|
||||
// view-line space, which moves the selection. We preserve the selection's change-line
|
||||
// ordinal, which is unchanged (the line isn't consumed, unlike staging) and
|
||||
// width-independent, re-expanding the hunk in hunk mode. A range collapses to a line,
|
||||
// as a staged range does.
|
||||
func revealSelectionAfterPatchToggle(c *ControllerCommon, mainViewName string, firstLineIdx int) {
|
||||
mainContext := c.Contexts().Normal
|
||||
if mainViewName == c.Contexts().NormalSecondary.GetViewName() {
|
||||
mainContext = c.Contexts().NormalSecondary
|
||||
}
|
||||
view := mainContext.GetView()
|
||||
|
||||
sel := mainContext.DiffSelectState()
|
||||
if sel.Mode == context.DiffSelectModeRange {
|
||||
sel.Mode = context.DiffSelectModeLine
|
||||
sel.RangeIsSticky = false
|
||||
}
|
||||
mode := sel.Mode
|
||||
|
||||
c.Helpers().Staging.RevealSelectionAfterStaging(view, view, firstLineIdx, func(viewLine int) {
|
||||
if mode == context.DiffSelectModeHunk {
|
||||
selectDiffHunk(c, mainContext, viewLine)
|
||||
} else {
|
||||
view.CancelRangeSelect()
|
||||
showSelectionAtLine(view, viewLine, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// patchFilename maps a diff line's absolute path to the key the patch builder stores
|
||||
// the file under — its repo-relative, slash-separated path.
|
||||
func patchFilename(c *ControllerCommon, absPath string) string {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ type IBaseContext interface {
|
|||
// (space), delegated to the side panel that owns the diff being shown. The
|
||||
// inclusive view-line range is the current selection (a single line, a range, or
|
||||
// a hunk).
|
||||
AddOnStageFocusedMainViewFn(func(mainViewName string, firstLineIdx int, lastLineIdx int) (focusViewName string, err error))
|
||||
AddOnStageFocusedMainViewFn(func(mainViewName string, firstLineIdx int, lastLineIdx int) error)
|
||||
// And for toggling the selected line(s) into/out of the custom patch from the
|
||||
// focused main view (space), when the panel beneath builds a patch rather than
|
||||
// staging.
|
||||
|
|
@ -338,14 +338,13 @@ type HasKeybindings interface {
|
|||
// that belongs to your panel while the main view is already focused.
|
||||
GetOnClickFocusedMainView() func(mainViewName string, clickedLineIdx int) error
|
||||
|
||||
// Implement this in a side-panel controller to stage/unstage (or, later, add to
|
||||
// the custom patch) the selected diff line(s) when the user presses space in the
|
||||
// focused main view. The inclusive view-line range is the current selection (a
|
||||
// single line, a range, or a hunk). It returns the name of the focused main view
|
||||
// that should hold focus afterwards — staging/unstaging can move the acted-on
|
||||
// side to the other pane — or "" when nothing was done. Return a nil func to do
|
||||
// nothing.
|
||||
GetOnStageFocusedMainView() func(mainViewName string, firstLineIdx int, lastLineIdx int) (focusViewName string, err error)
|
||||
// Implement this in a side-panel controller to stage/unstage the selected diff
|
||||
// line(s) when the user presses space in the focused main view. The inclusive
|
||||
// view-line range is the current selection (a single line, a range, or a hunk).
|
||||
// The handler re-renders the diff and re-establishes the selection itself
|
||||
// (staging/unstaging can move the acted-on side to the other pane, which the
|
||||
// handler then focuses). Return a nil func to do nothing.
|
||||
GetOnStageFocusedMainView() func(mainViewName string, firstLineIdx int, lastLineIdx int) error
|
||||
|
||||
// Implement this in a side-panel controller to toggle the selected diff line(s)
|
||||
// into or out of the custom patch when the user presses space in the focused main
|
||||
|
|
|
|||
Loading…
Reference in a new issue