Repository: jesseduffield/lazygit (language: go)

Issue:
Crash when filtering by author
**Describe the bug**
The application crashes when filtering the commits by user (but not always, can't really pinpoint it, it's something when refreshing the view).

**To Reproduce**
Steps to reproduce the behavior:

1. Open `lazygit` in the `lazygit` repo
2. Go to `commits` panel
3. Filter the commits by author, e.g. `Luke Swan` (note: can pretty consistently crash it with this author or myself, but not all of them)
4. Exit that view, press `<c-s>` again and just press `Filter by <the current author>`
5. Crash

**Expected behavior**
No crashes.

**Screenshots**
Asciinema recording [here](https://asciinema.org/a/XJ0t35wROY2fBlsZM9ujgH7eb).

**Version info:**
`lazygit` version: built latest `master` as of today, `git` version `2.46.0`


**Additional context**
It seems like [these columnPositions](https://github.com/jesseduffield/lazygit/blob/a3560eb451fcf375378a316cbc2f4e08e24bf756/pkg/gui/context/local_commits_context.go#L212) have only one element so both accesses are out of bounds.

**Note:** please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.

<!--
If you want to try and debug this issue yourself, you can run `lazygit --debug` in one terminal panel and `lazygit --logs` in another to view the logs.
-->



Discussion / hints:
I can reproduce it, I'll have a look. I think it happens whenever you are scrolled down so much that the selection index in the unfiltered list is larger than the length of the filtered list.

Fix patch:
diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go
index 0cb7a628c18..eecb161077e 100644
--- a/pkg/gui/context/local_commits_context.go
+++ b/pkg/gui/context/local_commits_context.go
@@ -202,6 +202,15 @@ func shouldShowGraph(c *ContextCommon) bool {
 }
 
 func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPositions []int, searchStr string) []gocui.SearchPosition {
+	if columnPositions == nil {
+		// This should never happen. We are being called at a time where our
+		// entire view content is scrolled out of view, so that we didn't draw
+		// anything the last time we rendered. If we run into a scenario where
+		// this happens, we should fix it, but until we found them all, at least
+		// make sure we don't crash.
+		return []gocui.SearchPosition{}
+	}
+
 	normalize := lo.Ternary(caseSensitive, func(s string) string { return s }, strings.ToLower)
 	return lo.FilterMap(commits, func(commit *models.Commit, idx int) (gocui.SearchPosition, bool) {
 		// The XStart and XEnd values are only used if the search string can't
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index e29879b9ddc..579bc9d3e33 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -765,8 +765,18 @@ func (self *RefreshHelper) refreshView(context types.Context) error {
 
 	err := self.c.PostRefreshUpdate(context)
 
-	// Re-applying the search must be done after re-rendering the view though,
-	// so that the "x of y" status is shown correctly.
-	self.searchHelper.ReApplySearch(context)
+	self.c.AfterLayout(func() error {
+		// Re-applying the search must be done after re-rendering the view though,
+		// so that the "x of y" status is shown correctly.
+		//
+		// Also, it must be done after layout, because otherwise FocusPoint
+		// hasn't been called yet (see ListContextTrait.FocusLine), which means
+		// that the scroll position might be such that the entire visible
+		// content is outside the viewport. And this would cause problems in
+		// searchModelCommits.
+		self.searchHelper.ReApplySearch(context)
+		return nil
+	})
+
 	return err
 }
diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go
index 3900d7f32b7..2d4232f33a6 100644
--- a/pkg/gui/controllers/local_commits_controller.go
+++ b/pkg/gui/controllers/local_commits_controller.go
@@ -207,14 +207,8 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
 			Description:       self.c.Tr.MarkAsBaseCommit,
 			Tooltip:           self.c.Tr.MarkAsBaseCommitTooltip,
 		},
-		// overriding these navigation keybindings because we might need to load
+		// overriding this navigation keybinding because we might need to load
 		// more commits on demand
-		{
-			Key:         opts.GetKey(opts.Config.Universal.StartSearch),
-			Handler:     self.openSearch,
-			Description: self.c.Tr.StartSearch,
-			Tag:         "navigation",
-		},
 		{
 			Key:         opts.GetKey(opts.Config.Universal.GotoBottom),
 			Handler:     self.gotoBottom,
@@ -228,6 +222,14 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
 	}
 
 	bindings := append(outsideFilterModeBindings, []*types.Binding{
+		// overriding this navigation keybinding because we might need to load
+		// more commits on demand
+		{
+			Key:         opts.GetKey(opts.Config.Universal.StartSearch),
+			Handler:     self.openSearch,
+			Description: self.c.Tr.StartSearch,
+			Tag:         "navigation",
+		},
 		{
 			Key:               opts.GetKey(opts.Config.Commits.AmendToCommit),
 			Handler:           self.withItem(self.amendTo),
diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go
index f9bdb4eb750..234a84e5c73 100644
--- a/pkg/gui/presentation/commits.go
+++ b/pkg/gui/presentation/commits.go
@@ -65,7 +65,7 @@ func GetCommitListDisplayStrings(
 		return nil
 	}
 
-	if startIdx > len(commits) {
+	if startIdx >= len(commits) {
 		return nil
 	}
 
diff --git a/pkg/utils/formatting.go b/pkg/utils/formatting.go
index b7817346ab7..b13a2ffa8ed 100644
--- a/pkg/utils/formatting.go
+++ b/pkg/utils/formatting.go
@@ -54,6 +54,10 @@ func WithPadding(str string, padding int, alignment Alignment) string {
 // returns a list of strings that should be joined with "\n", and an array of
 // the column positions
 func RenderDisplayStrings(displayStringsArr [][]string, columnAlignments []Alignment) ([]string, []int) {
+	if len(displayStringsArr) == 0 {
+		return []string{}, nil
+	}
+
 	displayStringsArr, columnAlignments, removedColumns := excludeBlankColumns(displayStringsArr, columnAlignments)
 	padWidths := getPadWidths(displayStringsArr)
 	columnConfigs := make([]ColumnConfig, len(padWidths))


Test patch: