Skip to content

Commit 3375cc1

Browse files
Merge pull request #2196 from jesseduffield/no-glitchy-render-to-main
2 parents a77aa4d + ed98b60 commit 3375cc1

File tree

9 files changed

+231
-23
lines changed

9 files changed

+231
-23
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ require (
1818
github.com/integrii/flaggy v1.4.0
1919
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
2020
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
21-
github.com/jesseduffield/gocui v0.3.1-0.20221001154429-72c39318a83d
21+
github.com/jesseduffield/gocui v0.3.1-0.20221003033055-3b1444b7ce1c
2222
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
2323
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
2424
github.com/jesseduffield/yaml v2.1.0+incompatible

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
7272
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
7373
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 h1:GOQrmaE8i+KEdB8NzAegKYd4tPn/inM0I1uo0NXFerg=
7474
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
75-
github.com/jesseduffield/gocui v0.3.1-0.20221001154429-72c39318a83d h1:OTUa2dO3IvnY53QWCABkKJK9v5yvs3+uv3RMbG698S0=
76-
github.com/jesseduffield/gocui v0.3.1-0.20221001154429-72c39318a83d/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
75+
github.com/jesseduffield/gocui v0.3.1-0.20221003033055-3b1444b7ce1c h1:mbOoXlqOzc243zNV71pDxeiEof8IRRw2ZJzVXm/RLjc=
76+
github.com/jesseduffield/gocui v0.3.1-0.20221003033055-3b1444b7ce1c/go.mod h1:znJuCDnF2Ph40YZSlBwdX/4GEofnIoWLGdT4mK5zRAU=
7777
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
7878
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
7979
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=

pkg/gui/gui.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ type GuiRepoState struct {
185185
// WindowViewNameMap is a mapping of windows to the current view of that window.
186186
// Some views move between windows for example the commitFiles view and when cycling through
187187
// side windows we need to know which view to give focus to for a given window
188-
WindowViewNameMap map[string]string
188+
WindowViewNameMap *utils.ThreadSafeMap[string, string]
189189

190190
// tells us whether we've set up our views for the current repo. We'll need to
191191
// do this whenever we switch back and forth between repos to get the views

pkg/gui/main_panels.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,35 @@ func (gui *Gui) runTaskForView(view *gocui.View, task types.UpdateTask) error {
2727
}
2828

2929
func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) {
30-
gui.setWindowContext(pair.Main)
31-
gui.moveToTopOfWindow(pair.Main)
30+
gui.moveMainContextToTop(pair.Main)
3231
if pair.Secondary != nil {
33-
gui.setWindowContext(pair.Secondary)
34-
gui.moveToTopOfWindow(pair.Secondary)
32+
gui.moveMainContextToTop(pair.Secondary)
33+
}
34+
}
35+
36+
func (gui *Gui) moveMainContextToTop(context types.Context) {
37+
gui.setWindowContext(context)
38+
39+
view := context.GetView()
40+
41+
topView := gui.topViewInWindow(context.GetWindowName())
42+
if topView == nil {
43+
gui.Log.Error("unexpected: topView is nil")
44+
return
45+
}
46+
47+
if topView != view {
48+
// We need to copy the content to avoid a flicker effect: If we're flicking
49+
// through files in the files panel, we use a different view to render the
50+
// files vs the directories, and if you select dir A, then file B, then dir
51+
// C, you'll briefly see dir A's contents again before the view is updated.
52+
// So here we're copying the content from the top window to avoid that
53+
// flicker effect.
54+
gui.g.CopyContent(topView, view)
55+
56+
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
57+
gui.Log.Error(err)
58+
}
3559
}
3660
}
3761

pkg/gui/window.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package gui
33
import (
44
"fmt"
55

6+
"github.com/jesseduffield/gocui"
67
"github.com/jesseduffield/lazygit/pkg/gui/context"
78
"github.com/jesseduffield/lazygit/pkg/gui/types"
9+
"github.com/jesseduffield/lazygit/pkg/utils"
810
"github.com/samber/lo"
911
)
1012

@@ -14,18 +16,18 @@ import (
1416
// space. Right now most windows are 1:1 with views, except for commitFiles which
1517
// is a view that moves between windows
1618

17-
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) map[string]string {
18-
result := map[string]string{}
19+
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
20+
result := utils.NewThreadSafeMap[string, string]()
1921

2022
for _, context := range contextTree.Flatten() {
21-
result[context.GetWindowName()] = context.GetViewName()
23+
result.Set(context.GetWindowName(), context.GetViewName())
2224
}
2325

2426
return result
2527
}
2628

2729
func (gui *Gui) getViewNameForWindow(window string) string {
28-
viewName, ok := gui.State.WindowViewNameMap[window]
30+
viewName, ok := gui.State.WindowViewNameMap.Get(window)
2931
if !ok {
3032
panic(fmt.Sprintf("Viewname not found for window: %s", window))
3133
}
@@ -50,7 +52,7 @@ func (gui *Gui) setWindowContext(c types.Context) {
5052
gui.resetWindowContext(c)
5153
}
5254

53-
gui.State.WindowViewNameMap[c.GetWindowName()] = c.GetViewName()
55+
gui.State.WindowViewNameMap.Set(c.GetWindowName(), c.GetViewName())
5456
}
5557

5658
func (gui *Gui) currentWindow() string {
@@ -59,39 +61,57 @@ func (gui *Gui) currentWindow() string {
5961

6062
// assumes the context's windowName has been set to the new window if necessary
6163
func (gui *Gui) resetWindowContext(c types.Context) {
62-
for windowName, viewName := range gui.State.WindowViewNameMap {
64+
for _, windowName := range gui.State.WindowViewNameMap.Keys() {
65+
viewName, ok := gui.State.WindowViewNameMap.Get(windowName)
66+
if !ok {
67+
continue
68+
}
6369
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
6470
for _, context := range gui.State.Contexts.Flatten() {
6571
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
66-
gui.State.WindowViewNameMap[windowName] = context.GetViewName()
72+
gui.State.WindowViewNameMap.Set(windowName, context.GetViewName())
6773
}
6874
}
6975
}
7076
}
7177
}
7278

73-
func (gui *Gui) moveToTopOfWindow(context types.Context) {
79+
// moves given context's view to the top of the window and returns
80+
// true if the view was not already on top.
81+
func (gui *Gui) moveToTopOfWindow(context types.Context) bool {
7482
view := context.GetView()
7583
if view == nil {
76-
return
84+
return false
7785
}
7886

7987
window := context.GetWindowName()
8088

89+
topView := gui.topViewInWindow(window)
90+
91+
if view.Name() == topView.Name() {
92+
return false
93+
} else {
94+
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
95+
gui.Log.Error(err)
96+
}
97+
98+
return true
99+
}
100+
}
101+
102+
func (gui *Gui) topViewInWindow(windowName string) *gocui.View {
81103
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
82-
viewNamesInWindow := gui.viewNamesInWindow(window)
104+
viewNamesInWindow := gui.viewNamesInWindow(windowName)
83105

84106
// The views list is ordered highest-last, so we're grabbing the last view of the window
85-
topView := view
107+
var topView *gocui.View
86108
for _, currentView := range gui.g.Views() {
87109
if lo.Contains(viewNamesInWindow, currentView.Name()) {
88110
topView = currentView
89111
}
90112
}
91113

92-
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
93-
gui.Log.Error(err)
94-
}
114+
return topView
95115
}
96116

97117
func (gui *Gui) viewNamesInWindow(windowName string) []string {

pkg/utils/thread_safe_map.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package utils
2+
3+
import "sync"
4+
5+
type ThreadSafeMap[K comparable, V any] struct {
6+
mutex sync.RWMutex
7+
8+
innerMap map[K]V
9+
}
10+
11+
func NewThreadSafeMap[K comparable, V any]() *ThreadSafeMap[K, V] {
12+
return &ThreadSafeMap[K, V]{
13+
innerMap: make(map[K]V),
14+
}
15+
}
16+
17+
func (m *ThreadSafeMap[K, V]) Get(key K) (V, bool) {
18+
m.mutex.RLock()
19+
defer m.mutex.RUnlock()
20+
21+
value, ok := m.innerMap[key]
22+
return value, ok
23+
}
24+
25+
func (m *ThreadSafeMap[K, V]) Set(key K, value V) {
26+
m.mutex.Lock()
27+
defer m.mutex.Unlock()
28+
29+
m.innerMap[key] = value
30+
}
31+
32+
func (m *ThreadSafeMap[K, V]) Delete(key K) {
33+
m.mutex.Lock()
34+
defer m.mutex.Unlock()
35+
36+
delete(m.innerMap, key)
37+
}
38+
39+
func (m *ThreadSafeMap[K, V]) Keys() []K {
40+
m.mutex.RLock()
41+
defer m.mutex.RUnlock()
42+
43+
keys := make([]K, 0, len(m.innerMap))
44+
for key := range m.innerMap {
45+
keys = append(keys, key)
46+
}
47+
48+
return keys
49+
}
50+
51+
func (m *ThreadSafeMap[K, V]) Values() []V {
52+
m.mutex.RLock()
53+
defer m.mutex.RUnlock()
54+
55+
values := make([]V, 0, len(m.innerMap))
56+
for _, value := range m.innerMap {
57+
values = append(values, value)
58+
}
59+
60+
return values
61+
}
62+
63+
func (m *ThreadSafeMap[K, V]) Len() int {
64+
m.mutex.RLock()
65+
defer m.mutex.RUnlock()
66+
67+
return len(m.innerMap)
68+
}
69+
70+
func (m *ThreadSafeMap[K, V]) Clear() {
71+
m.mutex.Lock()
72+
defer m.mutex.Unlock()
73+
74+
m.innerMap = make(map[K]V)
75+
}
76+
77+
func (m *ThreadSafeMap[K, V]) IsEmpty() bool {
78+
m.mutex.RLock()
79+
defer m.mutex.RUnlock()
80+
81+
return len(m.innerMap) == 0
82+
}
83+
84+
func (m *ThreadSafeMap[K, V]) Has(key K) bool {
85+
m.mutex.RLock()
86+
defer m.mutex.RUnlock()
87+
88+
_, ok := m.innerMap[key]
89+
return ok
90+
}

pkg/utils/thread_safe_map_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package utils
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestThreadSafeMap(t *testing.T) {
8+
m := NewThreadSafeMap[int, int]()
9+
10+
m.Set(1, 1)
11+
m.Set(2, 2)
12+
m.Set(3, 3)
13+
14+
if m.Len() != 3 {
15+
t.Errorf("Expected length to be 3, got %d", m.Len())
16+
}
17+
18+
if !m.Has(1) {
19+
t.Errorf("Expected to have key 1")
20+
}
21+
22+
if m.Has(4) {
23+
t.Errorf("Expected to not have key 4")
24+
}
25+
26+
if _, ok := m.Get(1); !ok {
27+
t.Errorf("Expected to have key 1")
28+
}
29+
30+
if _, ok := m.Get(4); ok {
31+
t.Errorf("Expected to not have key 4")
32+
}
33+
34+
m.Delete(1)
35+
36+
if m.Has(1) {
37+
t.Errorf("Expected to not have key 1")
38+
}
39+
40+
m.Clear()
41+
42+
if m.Len() != 0 {
43+
t.Errorf("Expected length to be 0, got %d", m.Len())
44+
}
45+
}
46+
47+
func TestThreadSafeMapConcurrentReadWrite(t *testing.T) {
48+
m := NewThreadSafeMap[int, int]()
49+
50+
go func() {
51+
for i := 0; i < 10000; i++ {
52+
m.Set(0, 0)
53+
}
54+
}()
55+
56+
for i := 0; i < 10000; i++ {
57+
m.Get(0)
58+
}
59+
}

vendor/github.com/jesseduffield/gocui/gui.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/modules.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
172172
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
173173
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
174174
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
175-
# github.com/jesseduffield/gocui v0.3.1-0.20221001154429-72c39318a83d
175+
# github.com/jesseduffield/gocui v0.3.1-0.20221003033055-3b1444b7ce1c
176176
## explicit; go 1.12
177177
github.com/jesseduffield/gocui
178178
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10

0 commit comments

Comments
 (0)