Use the default select mode when diving into a patch explorer from a focused main view

Diving into staging or patch building from a focused main view (by
double-clicking a line, or pressing enter on the selected line) always
landed on a single-line selection. Entering the same views through the
side panel honours the UseHunkModeInStagingView config and selects the
whole hunk by default. The two ways in should agree, so that diving in
from the main view feels like the established flow.

A non-negative line index in NewState was overloaded for two intents:
clicking directly on the patch explorer view (where a single-line range
is the start of a drag) and diving in from the main view (where we want
the default select mode). Distinguish them with SelectLineInDefaultMode
on OnFocusOpts: the main-view entry points set it; the click-to-drag
path does not.

In hunk mode the selection covers the block of changes around the
clicked line. A context line has no surrounding changes, so we snap to
the next change line (as toggling hunk mode does); the clicked context
line itself is then not part of the selection.

This is a separate commit only because the branch is a throwaway
prototype; in a real history it would be folded into the commit that
introduces the focused-main-view enter behavior rather than landing on
top of it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-06-04 18:04:34 +02:00
parent ea4a1716d1
commit 0b11400a9d
7 changed files with 32 additions and 10 deletions

View file

@ -580,7 +580,7 @@ func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName
}
// Entered from the focused main view, so escaping returns there.
return self.c.Helpers().CommitFiles.EnterCommitFile(node, snapshot, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
return self.c.Helpers().CommitFiles.EnterCommitFile(node, snapshot, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: line, ClickedViewRealLineIdx: line, SelectLineInDefaultMode: true})
}
}

View file

@ -439,7 +439,7 @@ func (self *FilesController) GetOnClickFocusedMainView() func(mainViewName strin
}
}
return self.EnterFile(snapshot, types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
return self.EnterFile(snapshot, types.OnFocusOpts{ClickedWindowName: mainViewName, ClickedViewLineIdx: line, ClickedViewRealLineIdx: line, SelectLineInDefaultMode: true})
}
}

View file

@ -159,7 +159,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
oldState := context.GetState()
state := patch_exploring.NewState(diff, selectedLineIdx, selectedRealLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView)
state := patch_exploring.NewState(diff, selectedLineIdx, selectedRealLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView, opts.SelectLineInDefaultMode)
context.SetState(state)
if state == nil {
self.Escape()

View file

@ -74,11 +74,11 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
hunkMode := self.c.UserConfig().Gui.UseHunkModeInStagingView
mainContext.SetState(
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainSelectedRealLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode),
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainSelectedRealLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode, focusOpts.SelectLineInDefaultMode),
)
secondaryContext.SetState(
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondarySelectedRealLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode),
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondarySelectedRealLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode, focusOpts.SelectLineInDefaultMode),
)
mainState := mainContext.GetState()

View file

@ -88,7 +88,7 @@ func (self *SwitchToDiffFilesController) GetOnClickFocusedMainView() func(mainVi
context.GetViewTrait().FocusPoint(
context.ModelIndexToViewIndex(idx), false)
node = context.GetSelected()
return self.c.Helpers().CommitFiles.EnterCommitFile(node, snapshot, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: line, ClickedViewRealLineIdx: line})
return self.c.Helpers().CommitFiles.EnterCommitFile(node, snapshot, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: line, ClickedViewRealLineIdx: line, SelectLineInDefaultMode: true})
}
}

View file

@ -45,7 +45,7 @@ const (
HUNK
)
func NewState(diff string, selectedLineIdx int, selectedRealLineIdx int, view *gocui.View, oldState *State, useHunkModeByDefault bool) *State {
func NewState(diff string, selectedLineIdx int, selectedRealLineIdx int, view *gocui.View, oldState *State, useHunkModeByDefault bool, selectLineInDefaultMode bool) *State {
if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 {
// if we're here then we can return the old state. If selectedLineIdx was not -1
// then that would mean we were trying to click and potentially drag a range, which
@ -84,14 +84,28 @@ func NewState(diff string, selectedLineIdx int, selectedRealLineIdx int, view *g
userEnabledHunkMode = oldState.userEnabledHunkMode
}
// if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line
// A non-negative line index means we were given a specific line to select:
// either by clicking or pressing enter on a line in a focused main view, or
// by clicking directly on the patch explorer view to focus it.
if selectedLineIdx >= 0 {
// Clamp to the number of wrapped view lines; index might be out of
// bounds if a custom diff renderer is being used which produces more lines
selectedLineIdx = min(selectedLineIdx, len(patchLineIndices)-1)
selectMode = RANGE
rangeStartLineIdx = selectedLineIdx
if selectLineInDefaultMode {
// Diving in from a focused main view: keep the default select mode
// computed above. In hunk mode the selection covers the block of
// changes around the line, so snap to a change line if the given one
// is a context line (just as toggling hunk mode does).
if selectMode == HUNK {
selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(patchLineIndices[selectedLineIdx])]
}
} else {
// Clicking directly on the view starts a range selection that can be
// extended by dragging.
selectMode = RANGE
rangeStartLineIdx = selectedLineIdx
}
} else if oldState != nil {
// if we previously had a selectMode of RANGE, we want that to now be line again (or hunk, if that's the default)
if oldState.selectMode != RANGE {

View file

@ -261,6 +261,14 @@ type OnFocusOpts struct {
// If not -1, takes precedence over ClickedViewLineIdx.
ClickedViewRealLineIdx int
// When entering a patch explorer (staging or patch building) by clicking or
// pressing enter on a line in a focused main view, we select that line using
// the default select mode (hunk or line, per the UseHunkModeInStagingView
// config), the same as when entering through the side panel. Clicking
// directly on the patch explorer view instead starts a range selection that
// can be extended by dragging.
SelectLineInDefaultMode bool
ScrollSelectionIntoView bool
}