diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index cc5e6c0e4..0a06d1981 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -311,6 +311,19 @@ func (v *View) refreshSearchPositionsIfNeeded() { } } +// RefreshSearch runs the search again over content the view has just been re-rendered +// with, and shows the "x of y" status of what it finds. The view stays where it is: the +// position in the content is the user's, and the search follows it rather than moving +// it. +func (v *View) RefreshSearch() { + if !v.IsSearching() { + return + } + + v.UpdateSearchResults(v.searcher.searchString, v.searcher.modelSearchResults) + v.renderSearchStatus(v.searcher.currentSearchIndex, len(v.searcher.searchPositions)) +} + func (v *View) gotoNextMatch() error { v.refreshSearchPositions() diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go index a0efcb14c..bc4a4219e 100644 --- a/pkg/gui/main_panels.go +++ b/pkg/gui/main_panels.go @@ -2,6 +2,7 @@ package gui import ( "github.com/jesseduffield/lazygit/pkg/gocui" + "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -139,3 +140,17 @@ func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) { func (gui *Gui) splitMainPanel(splitMainPanel bool) { gui.State.SplitMainPanel = splitMainPanel } + +// reApplySearch runs a search the view holds again over the content a render has just +// finished putting there, so that the matches highlighted and the "x of y" status +// describe what the view shows now rather than what it showed when the search was +// typed. Call it once the content is final. +func (gui *Gui) reApplySearch(view *gocui.View) { + // While the prompt is open, the search view holds what the user is typing, and the + // status would be written over it. + if gui.State.ContextMgr.Current().GetKey() == context.SEARCH_CONTEXT_KEY { + return + } + + view.RefreshSearch() +} diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index 5e5295639..3ed141d68 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -91,6 +91,7 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error { f := func(tasks.TaskOpts) error { return gui.g.OnUIThreadAndWaitBackground(func() { gui.c.SetViewContent(view, str) + gui.reApplySearch(view) }) } @@ -108,6 +109,7 @@ func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX in return gui.g.OnUIThreadAndWaitBackground(func() { gui.c.SetViewContent(view, str) view.SetOrigin(originX, originY) + gui.reApplySearch(view) }) } @@ -125,6 +127,7 @@ func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) e return gui.g.OnUIThreadAndWaitBackground(func() { gui.c.ResetViewOrigin(view) gui.c.SetViewContent(view, str) + gui.reApplySearch(view) }) } @@ -170,6 +173,8 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { view.SetOrigin(0, newOriginY) } + + gui.reApplySearch(view) }, func() { view.SetOrigin(0, 0) diff --git a/pkg/integration/tests/filter_and_search/search_status_after_a_rerender.go b/pkg/integration/tests/filter_and_search/search_status_after_a_rerender.go new file mode 100644 index 000000000..8116049b9 --- /dev/null +++ b/pkg/integration/tests/filter_and_search/search_status_after_a_rerender.go @@ -0,0 +1,45 @@ +package filter_and_search + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var SearchStatusAfterARerender = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "The search status counts the matches in a diff that has been rendered again", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file1", + "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nNEEDLE\nline 11\nline 12\nline 13\nline 14\n") + shell.Commit("one") + + // Four lines above NEEDLE, so that it is context at a context size of 4 but + // not at 3. + shell.UpdateFile("file1", + "line 1\nline 2\nline 3\nline 4\nline 5\nchanged\nline 7\nline 8\nline 9\nNEEDLE\nline 11\nline 12\nline 13\nline 14\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + Press(keys.Universal.FocusMainView) + + t.Views().Main(). + IsFocused(). + Content(DoesNotContain("NEEDLE")). + FilterOrSearch("NEEDLE") + + t.Views().Search().Content(Contains("No matches for 'NEEDLE'")) + + // A wider context brings NEEDLE into the diff, and the search counts it. + t.Views().Main(). + Press(keys.Universal.IncreaseContextInDiffView). + Tap(func() { + t.ExpectToast(Equals("Changed diff context size to 4")) + }). + Content(Contains("NEEDLE")) + + t.Views().Search().Content(Contains("matches for 'NEEDLE' (1 of 1)")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 1324a4160..3d9edadfe 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -278,6 +278,7 @@ var tests = []*components.IntegrationTest{ filter_and_search.NestedFilterTransient, filter_and_search.NewSearch, filter_and_search.SearchALongDiff, + filter_and_search.SearchStatusAfterARerender, filter_and_search.StageAllStagesOnlyTrackedFilesInTrackedOnlyFilter, filter_and_search.StagingFolderStagesOnlyTrackedFilesInTrackedOnlyFilter, filter_by_author.SelectAuthor,