mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 23:56:24 -04:00
Hide the focused main view's selection when there's no diff to act on
The focused main view always showed a selection while it held focus, even when
its content was a placeholder ("No changed files", a merge-conflict message)
rather than a diff — so pressing a navigation key conjured a highlighted line
over "No changed files", and discarding the last change (or changes vanishing
outside lazygit) left a stale selection behind.
A selection should only show when there are change lines to act on. Two
moments establish or change the content:
- Focus: showInitialDiffSelection now leaves the selection off when the view
has no change lines (ViewHasChangeLines), rather than highlighting a stray
line at the top.
- Render: the side panel's render-to-main is where it decides between a diff
and a placeholder, so that's where the selection's visibility is set —
updateFocusedMainViewSelectionVisibility shows it only on the focused pane
and only when a diff is being rendered. This covers the refresh cases that
focus can't: discarding the last change, and changes disappearing or
reappearing outside lazygit, all hide or restore the selection on the next
refresh. (Focusing reuses the already-rendered content rather than
re-rendering, which is why focus needs its own check.)
Wired into the files panel, where placeholders occur; the commit panels always
render a diff. Adds a SelectionIsShown/SelectionIsHidden test assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
26dcab23ae
commit
252a26cbc4
|
|
@ -250,6 +250,11 @@ func (self *FilesController) GetOnRenderToMain() func() {
|
|||
self.c.Helpers().Diff.WithDiffModeCheck(func() {
|
||||
node := self.context().GetSelected()
|
||||
|
||||
// Default the focused-main-view selection off; the real-diff branch below
|
||||
// turns it back on. Every other outcome (no file, merge conflict) renders
|
||||
// non-diff content with nothing to select.
|
||||
updateFocusedMainViewSelectionVisibility(self.c, false, false)
|
||||
|
||||
if node == nil {
|
||||
self.renderToMainWithTask(types.NewRenderStringTask(self.c.Tr.NoChangedFiles))
|
||||
return
|
||||
|
|
@ -398,6 +403,11 @@ func (self *FilesController) renderWorkingTreeDiff(node *filetree.FileNode) {
|
|||
}
|
||||
}
|
||||
|
||||
// A real diff is being shown, so the focused main view has something to select:
|
||||
// restore the selection on whichever pane is focused (the secondary only when
|
||||
// the diff is split across both).
|
||||
updateFocusedMainViewSelectionVisibility(self.c, true, split)
|
||||
|
||||
self.c.RenderToMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,20 @@ func (self *StagingHelper) FirstChangeLineInView(view *gocui.View) (int, bool) {
|
|||
return 0, false
|
||||
}
|
||||
|
||||
// ViewHasChangeLines reports whether view's displayed diff contains any change line
|
||||
// (an addition or deletion), i.e. whether there's anything to select. It's false when
|
||||
// the main view shows a non-diff placeholder ("No changed files", a merge message) or a
|
||||
// diff that is somehow all context — the cases where the focused main view should show
|
||||
// no selection.
|
||||
func (self *StagingHelper) ViewHasChangeLines(view *gocui.View) bool {
|
||||
for _, resolved := range self.resolveDiffLines(view.DiffLineContents()) {
|
||||
if resolved.ok && resolved.info.IsChange() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ChangeBlockBounds returns the view-line range [start, end] of the change block
|
||||
// (lazygit's notion of a hunk; see AdjacentChangeBlock) to select when entering or
|
||||
// moving in hunk mode in view's displayed diff. The block is the one containing
|
||||
|
|
|
|||
|
|
@ -251,6 +251,12 @@ func (self *MainViewController) togglePanel() error {
|
|||
func showInitialDiffSelection(c *ControllerCommon, mainContext *context.MainContext) {
|
||||
resetDiffSelectMode(mainContext)
|
||||
view := mainContext.GetView()
|
||||
// Nothing to act on (the main view shows "No changed files" or another non-diff
|
||||
// placeholder): show no selection at all rather than highlighting a stray line.
|
||||
if !c.Helpers().Staging.ViewHasChangeLines(view) {
|
||||
view.Highlight = false
|
||||
return
|
||||
}
|
||||
target, ok := c.Helpers().Staging.FirstChangeLineInView(view)
|
||||
if !ok {
|
||||
showSelectionAtLine(view, view.OriginY(), true)
|
||||
|
|
@ -264,6 +270,25 @@ func showInitialDiffSelection(c *ControllerCommon, mainContext *context.MainCont
|
|||
showSelectionAtLine(view, target, true)
|
||||
}
|
||||
|
||||
// updateFocusedMainViewSelectionVisibility shows or hides the focused-main-view selection
|
||||
// to match what a side panel is rendering into the main view, called from the panel's
|
||||
// render-to-main so the selection tracks content changes (a refresh after the last change
|
||||
// is discarded, or changes vanishing / appearing outside lazygit). A selection is shown
|
||||
// only on the main pane that currently holds focus, and only when it's rendering a diff
|
||||
// (something to act on) — never over "No changed files" or a merge-conflict message.
|
||||
// normalHasDiff/secondaryHasDiff say whether each pane is being given a diff; the caller
|
||||
// knows this from which content it's about to render (the rendered content can't be read
|
||||
// here, since the render it triggers is asynchronous). Initial keyboard/click focus is
|
||||
// handled separately by showInitialDiffSelection, since focusing reuses the already-
|
||||
// rendered content rather than re-rendering.
|
||||
func updateFocusedMainViewSelectionVisibility(c *ControllerCommon, normalHasDiff bool, secondaryHasDiff bool) {
|
||||
focusedKey := c.Context().CurrentStatic().GetKey()
|
||||
normal := c.Contexts().Normal
|
||||
secondary := c.Contexts().NormalSecondary
|
||||
normal.GetView().Highlight = normalHasDiff && focusedKey == normal.GetKey()
|
||||
secondary.GetView().Highlight = secondaryHasDiff && focusedKey == secondary.GetKey()
|
||||
}
|
||||
|
||||
// resetDiffSelectMode returns the focused main view to its default select mode — a
|
||||
// single line, no range — used whenever the selection is (re-)established from
|
||||
// scratch (on focus, on a click). The view's range anchor is cleared too so the
|
||||
|
|
|
|||
|
|
@ -367,6 +367,28 @@ func (self *ViewDriver) OriginYAtLeast(expected int) *ViewDriver {
|
|||
return self
|
||||
}
|
||||
|
||||
// SelectionIsShown asserts that the view is showing an active selection highlight.
|
||||
func (self *ViewDriver) SelectionIsShown() *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
view := self.getView()
|
||||
ok := view.Highlight && !view.HighlightInactive
|
||||
return ok, fmt.Sprintf("%s: expected an active selection to be shown, but it wasn't", self.context)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// SelectionIsHidden asserts that the view is showing no selection highlight (e.g. the
|
||||
// focused main view over "No changed files", where there's nothing to select).
|
||||
func (self *ViewDriver) SelectionIsHidden() *ViewDriver {
|
||||
self.t.assertWithRetries(func() (bool, string) {
|
||||
ok := !self.getView().Highlight
|
||||
return ok, fmt.Sprintf("%s: expected no selection to be shown, but one was", self.context)
|
||||
})
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// focus the view (assumes the view is a side-view)
|
||||
func (self *ViewDriver) Focus() *ViewDriver {
|
||||
viewName := self.getView().Name()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package staging
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var HideSelectionAfterDiscardingLastChange = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "After discarding the last change from the focused main view, the now-empty diff shows no selection",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Gui.UseHunkModeInStagingView = true
|
||||
config.GetUserConfig().Gui.SkipDiscardChangeWarning = true
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file1", "one\ntwo\nthree\n")
|
||||
shell.Commit("one")
|
||||
|
||||
// A single working-tree change, so discarding it empties the diff entirely.
|
||||
shell.UpdateFile("file1", "one\nTWO\nthree\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
SelectionIsShown().
|
||||
SelectedLines(
|
||||
Contains("-two"),
|
||||
Contains("+TWO"),
|
||||
).
|
||||
Press(keys.Universal.Remove)
|
||||
|
||||
// The only change is gone, so the main view shows the placeholder with no
|
||||
// lingering selection.
|
||||
t.Views().Main().
|
||||
Content(Contains("No changed files")).
|
||||
SelectionIsHidden()
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package staging
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var NoSelectionWhenNoChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Focusing the main view when there are no changes shows no selection, and navigating doesn't conjure one",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.EmptyCommit("one")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
IsEmpty().
|
||||
Press(keys.Universal.FocusMainView)
|
||||
|
||||
// There's nothing to act on, so the focused main view shows the placeholder with
|
||||
// no selection — and a navigation key just scrolls rather than conjuring one.
|
||||
t.Views().Main().
|
||||
IsFocused().
|
||||
Content(Contains("No changed files")).
|
||||
SelectionIsHidden().
|
||||
Press(keys.Universal.GotoTop)
|
||||
|
||||
t.Views().Main().
|
||||
SelectionIsHidden()
|
||||
},
|
||||
})
|
||||
|
|
@ -419,6 +419,8 @@ var tests = []*components.IntegrationTest{
|
|||
staging.DiscardFromStagedMainView,
|
||||
staging.FocusFollowsStagedSideToSecondaryAfterUnstaging,
|
||||
staging.FocusReturnsToMainAfterUnstagingLastStagedHunk,
|
||||
staging.HideSelectionAfterDiscardingLastChange,
|
||||
staging.NoSelectionWhenNoChanges,
|
||||
staging.Search,
|
||||
staging.SelectHunkOnFocusingMainView,
|
||||
staging.SelectNextChangeAfterUnstagingADeletion,
|
||||
|
|
|
|||
Loading…
Reference in a new issue