From 0b11400a9dca3fe8917d2ebec0a3133b3cbbaff8 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 4 Jun 2026 18:04:34 +0200 Subject: [PATCH] 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) --- .../controllers/commits_files_controller.go | 2 +- pkg/gui/controllers/files_controller.go | 2 +- .../helpers/patch_building_helper.go | 2 +- pkg/gui/controllers/helpers/staging_helper.go | 4 ++-- .../switch_to_diff_files_controller.go | 2 +- pkg/gui/patch_exploring/state.go | 22 +++++++++++++++---- pkg/gui/types/context.go | 8 +++++++ 7 files changed, 32 insertions(+), 10 deletions(-) diff --git a/pkg/gui/controllers/commits_files_controller.go b/pkg/gui/controllers/commits_files_controller.go index 85e377996..e42aa6e21 100644 --- a/pkg/gui/controllers/commits_files_controller.go +++ b/pkg/gui/controllers/commits_files_controller.go @@ -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}) } } diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 122eabfc1..d3dc4a304 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -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}) } } diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go index 69a48f188..55b6dbe1b 100644 --- a/pkg/gui/controllers/helpers/patch_building_helper.go +++ b/pkg/gui/controllers/helpers/patch_building_helper.go @@ -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() diff --git a/pkg/gui/controllers/helpers/staging_helper.go b/pkg/gui/controllers/helpers/staging_helper.go index 2c5aeddce..16b21e777 100644 --- a/pkg/gui/controllers/helpers/staging_helper.go +++ b/pkg/gui/controllers/helpers/staging_helper.go @@ -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() diff --git a/pkg/gui/controllers/switch_to_diff_files_controller.go b/pkg/gui/controllers/switch_to_diff_files_controller.go index 6624aac12..3528f2613 100644 --- a/pkg/gui/controllers/switch_to_diff_files_controller.go +++ b/pkg/gui/controllers/switch_to_diff_files_controller.go @@ -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}) } } diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index 0f2d1a4e7..3d1be981c 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -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 { diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index 40883fc04..0e2d551c2 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -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 }