From 26a784ef74e8f0a9530373b4a650ead10ab4999b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 18 Jun 2026 11:54:53 +0200 Subject: [PATCH] Select the first hunk when focusing the main view in hunk mode With hunk mode the default (gui.useHunkModeInStagingView, on by default), focusing the main view now selects the whole first visible change block rather than a single line, matching what entering the staging view does and what the just-added hunk staging expects. In line mode it still anchors on a single line, as before. Co-Authored-By: Claude Opus 4.8 (1M context) --- pkg/gui/controllers/main_view_controller.go | 37 ++++++++++----- .../select_hunk_on_focusing_main_view.go | 46 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 3 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 pkg/integration/tests/staging/select_hunk_on_focusing_main_view.go diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index fee7ad8f9..3f23207b0 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -223,16 +223,23 @@ func (self *MainViewController) togglePanel() error { } // showInitialDiffSelection turns on the focused main view's selection when entering -// a diff view without pointing at a specific line: on the first change line already -// visible (so the view doesn't jump), falling back to the current top line when none -// is visible (scrolled into trailing context, or not loaded that far yet). The -// select mode is reset to its default (a single line). +// a diff view without pointing at a specific line, anchored on the first change line +// already visible (so the view barely moves), falling back to the current top line +// when none is visible (scrolled into trailing context, or not loaded that far yet). +// With hunk mode configured as the default it selects the whole change block around +// that line, like entering the staging view does; otherwise a single line. func showInitialDiffSelection(c *ControllerCommon, mainContext *context.MainContext) { resetDiffSelectMode(mainContext) view := mainContext.GetView() target, ok := c.Helpers().Staging.FirstChangeLineInView(view) if !ok { - target = view.OriginY() + showSelectionAtLine(view, view.OriginY(), true) + return + } + if c.UserConfig().Gui.UseHunkModeInStagingView { + mainContext.DiffSelectState().Mode = context.DiffSelectModeHunk + selectDiffHunk(c, mainContext, target) + return } showSelectionAtLine(view, target, true) } @@ -365,16 +372,22 @@ func (self *MainViewController) prevFile() error { // its last, so the native range highlight spans the block. With no change block // there (the line is trailing context) it falls back to a single-line selection. func (self *MainViewController) selectHunkAround(changeViewLine int) { - v := self.context.GetView() - start, end, ok := self.c.Helpers().Staging.ChangeBlockBounds(v, changeViewLine) + selectDiffHunk(self.c, self.context, changeViewLine) +} + +// selectDiffHunk is the body of selectHunkAround, as a free function so the focus +// entry point (showInitialDiffSelection) can establish a hunk selection too. +func selectDiffHunk(c *ControllerCommon, mainContext *context.MainContext, changeViewLine int) { + view := mainContext.GetView() + start, end, ok := c.Helpers().Staging.ChangeBlockBounds(view, changeViewLine) if !ok { - self.sel().Mode = context.DiffSelectModeLine - v.CancelRangeSelect() - showSelectionAtLine(v, changeViewLine, true) + mainContext.DiffSelectState().Mode = context.DiffSelectModeLine + view.CancelRangeSelect() + showSelectionAtLine(view, changeViewLine, true) return } - v.SetRangeSelectStart(end) - showSelectionAtLine(v, start, true) + view.SetRangeSelectStart(end) + showSelectionAtLine(view, start, true) } // moveCursor moves the selection cursor by delta view lines (negative = up), with diff --git a/pkg/integration/tests/staging/select_hunk_on_focusing_main_view.go b/pkg/integration/tests/staging/select_hunk_on_focusing_main_view.go new file mode 100644 index 000000000..0860e0f33 --- /dev/null +++ b/pkg/integration/tests/staging/select_hunk_on_focusing_main_view.go @@ -0,0 +1,46 @@ +package staging + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var SelectHunkOnFocusingMainView = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "When hunk mode is the default, focusing the main view selects the first whole hunk, ready to stage", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Gui.UseHunkModeInStagingView = true + }, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file1", "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n") + shell.Commit("one") + + shell.UpdateFile("file1", "one\ntwo\nTHREE\nfour\nfive\nsix\nseven\neight\nNINE\nten\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Lines( + Contains("file1").IsSelected(), + ). + Press(keys.Universal.FocusMainView) + + // No key press: the first hunk is selected just by focusing, so space + // stages it straight away. + t.Views().Main(). + IsFocused(). + SelectedLines( + Contains("-three"), + Contains("+THREE"), + ). + PressPrimaryAction(). + Tap(func() { + t.Views().Secondary(). + ContainsLines( + Contains("-three"), + Contains("+THREE"), + ) + }) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 4dcfd9a6d..be61943e5 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -410,6 +410,7 @@ var tests = []*components.IntegrationTest{ staging.DiffContextChange, staging.DiscardAllChanges, staging.Search, + staging.SelectHunkOnFocusingMainView, staging.SelectNextLineAfterStagingInTwoHunkDiff, staging.SelectNextLineAfterStagingIsolatedAddedLine, staging.StageHunkFromMainView,