tabs: make it more thread safe

Protect the access to the tabs array and current index with a mutex.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Acked-by: Koni Marti <koni.marti@gmail.com>
This commit is contained in:
Robin Jarry 2022-07-14 22:10:23 +02:00
parent 171fefd209
commit 050d54a822
1 changed files with 68 additions and 13 deletions

View File

@ -2,6 +2,7 @@ package ui
import (
"io"
"sync"
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
@ -15,6 +16,7 @@ type Tabs struct {
TabContent *TabContent
curIndex int
history []int
m sync.Mutex
uiConfig *config.UIConfig
@ -55,16 +57,18 @@ func (tabs *Tabs) Add(content Drawable, name string, uiConf *config.UIConfig) *T
uiConf: uiConf,
}
tabs.tabs = append(tabs.tabs, tab)
tabs.Select(len(tabs.tabs) - 1)
tabs.selectPriv(len(tabs.tabs) - 1)
content.OnInvalidate(tabs.invalidateChild)
return tab
}
func (tabs *Tabs) Names() []string {
var names []string
tabs.m.Lock()
for _, tab := range tabs.tabs {
names = append(names, tab.Name)
}
tabs.m.Unlock()
return names
}
@ -81,6 +85,8 @@ func (tabs *Tabs) invalidateChild(d Drawable) {
}
func (tabs *Tabs) Remove(content Drawable) {
tabs.m.Lock()
defer tabs.m.Unlock()
indexToRemove := -1
for i, tab := range tabs.tabs {
if tab.Content == content {
@ -101,7 +107,7 @@ func (tabs *Tabs) Remove(content Drawable) {
}
} else if indexToRemove < tabs.curIndex {
// selected tab is now one to the left of where it was
tabs.Select(tabs.curIndex - 1)
tabs.selectPriv(tabs.curIndex - 1)
}
interactive, ok := tabs.tabs[tabs.curIndex].Content.(Interactive)
if ok {
@ -110,6 +116,8 @@ func (tabs *Tabs) Remove(content Drawable) {
}
func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name string) {
tabs.m.Lock()
defer tabs.m.Unlock()
replaceTab := &Tab{
Content: contentTarget,
Name: name,
@ -117,7 +125,7 @@ func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name stri
for i, tab := range tabs.tabs {
if tab.Content == contentSrc {
tabs.tabs[i] = replaceTab
tabs.Select(i)
tabs.selectPriv(i)
if c, ok := contentSrc.(io.Closer); ok {
c.Close()
}
@ -129,6 +137,8 @@ func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name stri
}
func (tabs *Tabs) Get(index int) *Tab {
tabs.m.Lock()
defer tabs.m.Unlock()
if index < 0 || index >= len(tabs.tabs) {
return nil
}
@ -136,6 +146,8 @@ func (tabs *Tabs) Get(index int) *Tab {
}
func (tabs *Tabs) Selected() *Tab {
tabs.m.Lock()
defer tabs.m.Unlock()
if tabs.curIndex < 0 || tabs.curIndex >= len(tabs.tabs) {
return nil
}
@ -143,6 +155,12 @@ func (tabs *Tabs) Selected() *Tab {
}
func (tabs *Tabs) Select(index int) bool {
tabs.m.Lock()
defer tabs.m.Unlock()
return tabs.selectPriv(index)
}
func (tabs *Tabs) selectPriv(index int) bool {
if index < 0 || index >= len(tabs.tabs) {
return false
}
@ -160,23 +178,33 @@ func (tabs *Tabs) Select(index int) bool {
}
func (tabs *Tabs) SelectName(name string) bool {
tabs.m.Lock()
defer tabs.m.Unlock()
for i, tab := range tabs.tabs {
if tab.Name == name {
return tabs.Select(i)
return tabs.selectPriv(i)
}
}
return false
}
func (tabs *Tabs) SelectPrevious() bool {
tabs.m.Lock()
defer tabs.m.Unlock()
index, ok := tabs.popHistory()
if !ok {
return false
}
return tabs.Select(index)
return tabs.selectPriv(index)
}
func (tabs *Tabs) MoveTab(to int, relative bool) {
tabs.m.Lock()
tabs.moveTabPriv(to, relative)
tabs.m.Unlock()
}
func (tabs *Tabs) moveTabPriv(to int, relative bool) {
from := tabs.curIndex
if relative {
@ -220,6 +248,8 @@ func (tabs *Tabs) MoveTab(to int, relative bool) {
}
func (tabs *Tabs) PinTab() {
tabs.m.Lock()
defer tabs.m.Unlock()
if tabs.tabs[tabs.curIndex].pinned {
return
}
@ -241,10 +271,12 @@ func (tabs *Tabs) PinTab() {
tabs.tabs[tabs.curIndex].pinned = true
tabs.tabs[tabs.curIndex].indexBeforePin = tabs.curIndex - pinEnd
tabs.MoveTab(pinEnd, false)
tabs.moveTabPriv(pinEnd, false)
}
func (tabs *Tabs) UnpinTab() {
tabs.m.Lock()
defer tabs.m.Unlock()
if !tabs.tabs[tabs.curIndex].pinned {
return
}
@ -262,23 +294,27 @@ func (tabs *Tabs) UnpinTab() {
tabs.tabs[tabs.curIndex].pinned = false
tabs.MoveTab(tabs.tabs[tabs.curIndex].indexBeforePin+pinEnd-1, false)
tabs.moveTabPriv(tabs.tabs[tabs.curIndex].indexBeforePin+pinEnd-1, false)
}
func (tabs *Tabs) NextTab() {
tabs.m.Lock()
next := tabs.curIndex + 1
if next >= len(tabs.tabs) {
next = 0
}
tabs.Select(next)
tabs.selectPriv(next)
tabs.m.Unlock()
}
func (tabs *Tabs) PrevTab() {
tabs.m.Lock()
next := tabs.curIndex - 1
if next < 0 {
next = len(tabs.tabs) - 1
}
tabs.Select(next)
tabs.selectPriv(next)
tabs.m.Unlock()
}
func (tabs *Tabs) pushHistory(index int) {
@ -316,6 +352,7 @@ func (tabs *Tabs) removeHistory(index int) {
// TODO: Color repository
func (strip *TabStrip) Draw(ctx *Context) {
x := 0
strip.parent.m.Lock()
for i, tab := range strip.tabs {
uiConfig := strip.uiConfig
if tab.uiConf != nil {
@ -339,6 +376,7 @@ func (strip *TabStrip) Draw(ctx *Context) {
break
}
}
strip.parent.m.Unlock()
ctx.Fill(x, 0, ctx.Width()-x, 1, ' ',
strip.uiConfig.GetStyle(config.STYLE_TAB))
}
@ -350,6 +388,8 @@ func (strip *TabStrip) Invalidate() {
}
func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
strip.parent.m.Lock()
defer strip.parent.m.Unlock()
changeFocus := func(focus bool) {
interactive, ok := strip.parent.tabs[strip.parent.curIndex].Content.(Interactive)
if ok {
@ -367,15 +407,23 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
return
}
unfocus()
strip.parent.Select(selectedTab)
strip.parent.selectPriv(selectedTab)
refocus()
case tcell.WheelDown:
unfocus()
strip.parent.NextTab()
index := strip.parent.curIndex + 1
if index >= len(strip.parent.tabs) {
index = 0
}
strip.parent.selectPriv(index)
refocus()
case tcell.WheelUp:
unfocus()
strip.parent.PrevTab()
index := strip.parent.curIndex - 1
if index < 0 {
index = len(strip.parent.tabs) - 1
}
strip.parent.selectPriv(index)
refocus()
case tcell.Button3:
selectedTab, ok := strip.clicked(localX, localY)
@ -383,7 +431,9 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
return
}
unfocus()
strip.parent.m.Unlock()
strip.parent.CloseTab(selectedTab)
strip.parent.m.Lock()
refocus()
}
}
@ -407,27 +457,32 @@ func (strip *TabStrip) clicked(mouseX int, mouseY int) (int, bool) {
}
func (content *TabContent) Children() []Drawable {
content.parent.m.Lock()
children := make([]Drawable, len(content.tabs))
for i, tab := range content.tabs {
children[i] = tab.Content
}
content.parent.m.Unlock()
return children
}
func (content *TabContent) Draw(ctx *Context) {
content.parent.m.Lock()
if content.curIndex >= len(content.tabs) {
width := ctx.Width()
height := ctx.Height()
ctx.Fill(0, 0, width, height, ' ',
content.uiConfig.GetStyle(config.STYLE_TAB))
}
tab := content.tabs[content.curIndex]
content.parent.m.Unlock()
tab.Content.Draw(ctx)
}
func (content *TabContent) MouseEvent(localX int, localY int, event tcell.Event) {
content.parent.m.Lock()
tab := content.tabs[content.curIndex]
content.parent.m.Unlock()
switch tabContent := tab.Content.(type) {
case Mouseable:
tabContent.MouseEvent(localX, localY, event)