diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go index 40ce86eae..57f9f586b 100644 --- a/pkg/gui/controllers/helpers/refresh_helper.go +++ b/pkg/gui/controllers/helpers/refresh_helper.go @@ -1413,7 +1413,7 @@ func (self *RefreshHelper) refreshStateFiles(captured capturedFilesState, env re self.c.Contexts().Files.GetView().Subtitle = self.c.Tr.FilterLabelConflictingFiles } } else if conflictFileCount == 0 && fileTreeViewModel.GetStatusFilter() == filetree.DisplayConflicted { - fileTreeViewModel.SetStatusFilter(filetree.DisplayAll) + fileTreeViewModel.SetStatusFilterPreservingSelection(filetree.DisplayAll) self.c.Contexts().Files.GetView().Subtitle = "" } diff --git a/pkg/gui/filetree/file_tree_view_model.go b/pkg/gui/filetree/file_tree_view_model.go index aabbbce7f..a5971f592 100644 --- a/pkg/gui/filetree/file_tree_view_model.go +++ b/pkg/gui/filetree/file_tree_view_model.go @@ -167,6 +167,31 @@ func (self *FileTreeViewModel) SetStatusFilter(filter FileTreeDisplayFilter) { self.IListCursor.SetSelection(0) } +func (self *FileTreeViewModel) SetStatusFilterPreservingSelection(filter FileTreeDisplayFilter) { + self.preserveSelection(func() { + self.SetStatusFilter(filter) + }) +} + +func (self *FileTreeViewModel) preserveSelection(f func()) { + selectedNode := self.GetSelected() + var selectedPath string + if selectedNode != nil { + selectedPath = selectedNode.GetInternalPath() + } + + f() + + if selectedPath != "" { + self.ExpandToPath(selectedPath) + if idx, found := self.GetIndexForPath(selectedPath); found { + self.SetSelection(idx) + return + } + } + self.ClampSelection() +} + // If we're going from flat to tree we want to select the same file. // If we're going from tree to flat and we have a file selected we want to select that. // If instead we've selected a directory we need to select the first file in that directory. @@ -233,22 +258,9 @@ func (self *FileTreeViewModel) GetFilter() string { } func (self *FileTreeViewModel) ClearFilter() { - selectedNode := self.GetSelected() - var selectedPath string - if selectedNode != nil { - selectedPath = selectedNode.GetInternalPath() - } - - self.IFileTree.SetTextFilter("", false) - - if selectedPath != "" { - self.ExpandToPath(selectedPath) - if idx, found := self.GetIndexForPath(selectedPath); found { - self.SetSelection(idx) - return - } - } - self.ClampSelection() + self.preserveSelection(func() { + self.IFileTree.SetTextFilter("", false) + }) } func (self *FileTreeViewModel) ReApplyFilter(useFuzzySearch bool) { diff --git a/pkg/gui/filetree/file_tree_view_model_test.go b/pkg/gui/filetree/file_tree_view_model_test.go new file mode 100644 index 000000000..c14c91ea8 --- /dev/null +++ b/pkg/gui/filetree/file_tree_view_model_test.go @@ -0,0 +1,32 @@ +package filetree + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/common" + "github.com/stretchr/testify/assert" +) + +func TestSetStatusFilterPreservingSelection(t *testing.T) { + files := []*models.File{ + {Path: "file1"}, + {Path: "file2", HasMergeConflicts: true}, + {Path: "file3", HasMergeConflicts: true}, + } + viewModel := NewFileTreeViewModel( + func() []*models.File { return files }, + common.NewDummyCommon(), + false, + ) + viewModel.SetTree() + viewModel.SetStatusFilter(DisplayConflicted) + viewModel.SetSelection(viewModel.Len() - 2) + viewModel.ToggleStickyRange() + viewModel.MoveSelectedLine(1) + + viewModel.SetStatusFilterPreservingSelection(DisplayAll) + + assert.Equal(t, "file3", viewModel.GetSelectedPath()) + assert.False(t, viewModel.IsSelectingRange()) +} diff --git a/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors_with_out_of_date_submodule.go b/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors_with_out_of_date_submodule.go index 1b95fd316..29db1a212 100644 --- a/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors_with_out_of_date_submodule.go +++ b/pkg/integration/tests/branch/rebase_conflicts_fix_build_errors_with_out_of_date_submodule.go @@ -75,8 +75,8 @@ var RebaseConflictsFixBuildErrorsWithOutOfDateSubmodule = NewIntegrationTest(New t.Views().Files(). Lines( - Equals("▼ /").IsSelected(), - Equals(" MM file"), + Equals("▼ /"), + Equals(" MM file").IsSelected(), Equals(" M submodule (submodule)"), Equals(" ?? untracked-file"), ) @@ -90,8 +90,8 @@ var RebaseConflictsFixBuildErrorsWithOutOfDateSubmodule = NewIntegrationTest(New t.Views().Files(). Lines( - Equals("▼ /").IsSelected(), - Equals(" M submodule (submodule)"), + Equals("▼ /"), + Equals(" M submodule (submodule)").IsSelected(), Equals(" ?? untracked-file"), ) diff --git a/pkg/integration/tests/conflicts/resolve_multiple_files.go b/pkg/integration/tests/conflicts/resolve_multiple_files.go index 5a8f9447e..b38f59ee2 100644 --- a/pkg/integration/tests/conflicts/resolve_multiple_files.go +++ b/pkg/integration/tests/conflicts/resolve_multiple_files.go @@ -34,6 +34,7 @@ var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{ Contains("First Change"), Contains("======="), ). + SelectNextItem(). PressPrimaryAction() t.Views().Files(). @@ -47,12 +48,14 @@ var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().MergeConflicts(). IsFocused(). SelectedLines( - Contains("<<<<<<< HEAD"), - Contains("First Change"), Contains("======="), + Contains("Second Change"), + Contains(">>>>>>>"), ). PressPrimaryAction() + t.Views().Files().SelectedLines(Contains("file2")) + t.Common().ContinueOnConflictsResolved("merge") }, }) diff --git a/pkg/integration/tests/file/discard_various_changes_range_select.go b/pkg/integration/tests/file/discard_various_changes_range_select.go index 16ecedd04..2199f1278 100644 --- a/pkg/integration/tests/file/discard_various_changes_range_select.go +++ b/pkg/integration/tests/file/discard_various_changes_range_select.go @@ -46,12 +46,12 @@ var DiscardVariousChangesRangeSelect = NewIntegrationTest(NewIntegrationTestArgs Cancel() }). Lines( - Equals("▼ /").IsSelected(), + Equals("▼ /"), Equals(" AM added-changed.txt"), Equals(" MD change-delete.txt"), Equals(" D delete-change.txt"), Equals(" D deleted-staged.txt"), - Equals(" D deleted.txt"), + Equals(" D deleted.txt").IsSelected(), Equals(" MM double-modded.txt"), Equals(" M modded-staged.txt"), Equals(" M modded.txt"), @@ -59,6 +59,7 @@ var DiscardVariousChangesRangeSelect = NewIntegrationTest(NewIntegrationTestArgs Equals(" ?? new.txt"), Equals(" R renamed.txt → renamed2.txt"), ). + NavigateToLine(Equals("▼ /")). Press(keys.Universal.ToggleRangeSelect). NavigateToLine(Contains("renamed.txt")). Press(keys.Universal.Remove). diff --git a/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go b/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go index 1a09cea7a..d9f99a703 100644 --- a/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go +++ b/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go @@ -83,11 +83,10 @@ var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Files(). Focus(). Lines( - Equals("▼ /").IsSelected(), - Equals(" M file1"), + Equals("▼ /"), + Equals(" M file1").IsSelected(), Equals(" M file2"), - ). - SelectNextItem() + ) t.Views().Main(). ContainsLines(