Repository: jesseduffield/lazygit (language: go)

Issue:
Files panel is empty when (many) new/changed files exist
**Describe the bug**
While git status shows that changed/new files exist, lazygit panel "Files" panel is empty.

This happens when number of new/changed files is really large (e.g. 90 thousands) - lazygit is aware of their existence but it takes 9 seconds (on my MacBook Pro M1) until anything is printed into Files panel. Until then, user feels like lazygit ignores the changes as there is no sign, lazygit is still trying to show somehting.


**To Reproduce**
Steps to reproduce the behavior:

1. Prepare new repository with simple initial commit:
```shell
$ mkdir manylazyfiles
$ cd manylazyfiles
$ touch README.md
$ git init
Initialized empty Git repository in manylazyfiles/.git/
$ git add README.md
$ git commit -m "Initial commit"
[master (root-commit) 45d7d58] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README.md
```
2. Prepare a folder with 90000 files
```shell
mkdir data
seq 90000 |xargs -I {} touch data/file-{}.txt
```
3. Check git status:
```shell
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	data/

nothing added to commit but untracked files present (use "git add" to track)
```
4. Open lazygit and watch content of the Files panel
```shell
lazygit
```
The panel is empty and lazygit seems ignoring the newly created files until like 9 seconds and then the files are shown properly.

Typical developer will give up after few seconds closing lazygit with unfair remark "ignorant".


**Expected behavior**
lazygit would print initially "Reading files..." into Files panel and would then replace it with whatever final result would finally get there.

This could happen always or (optimized variant) only if the content of the panel is not populated within a second.

**Screenshots**
Initial presentation with empty Files (during rendering large number of new items):
![obrazek](https://github.com/user-attachments/assets/e07d0afd-ed25-43ed-9769-ece083bdf8ab)

And after cca 9 seconds the files are finally shown:
![obrazek](https://github.com/user-attachments/assets/13d628b3-f180-403f-9e8a-3b9bd76823c5)

**Version info:**
```shell
$ lazygit --version
commit=, build date=, build source=homebrew, version=0.43.1, os=darwin, arch=arm64, git version=2.45.2
$ git --version
git version 2.45.2
```



Discussion / hints:
> lazygit would print initially "Reading files..." into Files panel and would then replace it with whatever final result would finally get there.

Before we do that, we should see if it's possible to optimize the code so that it doesn't take so long. There's really no reason why it should take several seconds to populate the files panel with a few thousand files.

A cursory profiling shows that almost all the time is spent inside the [`BuildTreeFromFiles`](https://github.com/jesseduffield/lazygit/blob/a3560eb451fcf375378a316cbc2f4e08e24bf756/pkg/gui/filetree/build_tree.go#L10) function, so it looks like we only need to speed up that function. Looking at that function (with squinted eyes), it does look like it has quadratic behavior when there are lots of files in the same directory, but I don't have time to investigate this further right now. Do you feel like looking into this?
Can I take a loot at it?

Fix patch:
diff --git a/pkg/gui/filetree/build_tree.go b/pkg/gui/filetree/build_tree.go
index c7c465e28e8..0193ba45c01 100644
--- a/pkg/gui/filetree/build_tree.go
+++ b/pkg/gui/filetree/build_tree.go
@@ -10,6 +10,8 @@ import (
 func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
 	root := &Node[models.File]{}
 
+	childrenMapsByNode := make(map[*Node[models.File]]map[string]*Node[models.File])
+
 	var curr *Node[models.File]
 	for _, file := range files {
 		splitPath := split(file.Name)
@@ -23,11 +25,19 @@ func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
 			}
 
 			path := join(splitPath[:i+1])
-			for _, existingChild := range curr.Children {
-				if existingChild.Path == path {
-					curr = existingChild
-					continue outer
-				}
+
+			var currNodeChildrenMap map[string]*Node[models.File]
+			var isCurrNodeMapped bool
+
+			if currNodeChildrenMap, isCurrNodeMapped = childrenMapsByNode[curr]; !isCurrNodeMapped {
+				currNodeChildrenMap = make(map[string]*Node[models.File])
+				childrenMapsByNode[curr] = currNodeChildrenMap
+			}
+
+			child, doesCurrNodeHaveChildAlready := currNodeChildrenMap[path]
+			if doesCurrNodeHaveChildAlready {
+				curr = child
+				continue outer
 			}
 
 			newChild := &Node[models.File]{
@@ -36,6 +46,8 @@ func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
 			}
 			curr.Children = append(curr.Children, newChild)
 
+			currNodeChildrenMap[path] = newChild
+
 			curr = newChild
 		}
 	}


Test patch: