tabs: make fields private

The Tabs object exposes an array of Tab objects and the current selected
index in that array. The these two fields are sometimes modified in
goroutines, which can lead to data races causing fatal out of bounds
accesses on the tab array.

Hide these fields as private API. Expose only what needs to be seen from
the outside. This will prepare for protecting concurrent access with
a lock in the next commit.

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 18:29:56 +02:00
parent c841f36513
commit 171fefd209
6 changed files with 145 additions and 126 deletions

View File

@ -35,9 +35,13 @@ func (Postpone) Execute(aerc *widgets.Aerc, args []string) error {
if acct == nil { if acct == nil {
return errors.New("No account selected") return errors.New("No account selected")
} }
composer, _ := aerc.SelectedTabContent().(*widgets.Composer) tab := aerc.SelectedTab()
if tab == nil {
return errors.New("No tab selected")
}
composer, _ := tab.Content.(*widgets.Composer)
config := composer.Config() config := composer.Config()
tabName := aerc.TabNames()[aerc.SelectedTabIndex()] tabName := tab.Name
if config.Postpone == "" { if config.Postpone == "" {
return errors.New("No Postpone location configured") return errors.New("No Postpone location configured")

View File

@ -43,8 +43,12 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
if len(args) > 1 { if len(args) > 1 {
return errors.New("Usage: send") return errors.New("Usage: send")
} }
composer, _ := aerc.SelectedTabContent().(*widgets.Composer) tab := aerc.SelectedTab()
tabName := aerc.TabNames()[aerc.SelectedTabIndex()] if tab == nil {
return errors.New("No selected tab")
}
composer, _ := tab.Content.(*widgets.Composer)
tabName := tab.Name
config := composer.Config() config := composer.Config()
if config.Outgoing == "" { if config.Outgoing == "" {

View File

@ -34,18 +34,11 @@ func (MoveTab) Execute(aerc *widgets.Aerc, args []string) error {
return fmt.Errorf("failed to parse index argument: %v", err) return fmt.Errorf("failed to parse index argument: %v", err)
} }
i := aerc.SelectedTabIndex() var relative bool
l := aerc.NumTabs() if strings.HasPrefix(joinedArgs, "+") || strings.HasPrefix(joinedArgs, "-") {
relative = true
if strings.HasPrefix(joinedArgs, "+") {
i = (i + n) % l
} else if strings.HasPrefix(joinedArgs, "-") {
i = (((i + n) % l) + l) % l
} else {
i = n
} }
aerc.MoveTab(n, relative)
aerc.MoveTab(i)
return nil return nil
} }

View File

@ -10,10 +10,10 @@ import (
) )
type Tabs struct { type Tabs struct {
Tabs []*Tab tabs []*Tab
TabStrip *TabStrip TabStrip *TabStrip
TabContent *TabContent TabContent *TabContent
Selected int curIndex int
history []int history []int
uiConfig *config.UIConfig uiConfig *config.UIConfig
@ -54,18 +54,26 @@ func (tabs *Tabs) Add(content Drawable, name string, uiConf *config.UIConfig) *T
Name: name, Name: name,
uiConf: uiConf, uiConf: uiConf,
} }
tabs.Tabs = append(tabs.Tabs, tab) tabs.tabs = append(tabs.tabs, tab)
tabs.TabStrip.Invalidate() tabs.Select(len(tabs.tabs) - 1)
content.OnInvalidate(tabs.invalidateChild) content.OnInvalidate(tabs.invalidateChild)
return tab return tab
} }
func (tabs *Tabs) Names() []string {
var names []string
for _, tab := range tabs.tabs {
names = append(names, tab.Name)
}
return names
}
func (tabs *Tabs) invalidateChild(d Drawable) { func (tabs *Tabs) invalidateChild(d Drawable) {
if tabs.Selected >= len(tabs.Tabs) { if tabs.curIndex >= len(tabs.tabs) {
return return
} }
if tabs.Tabs[tabs.Selected].Content == d { if tabs.tabs[tabs.curIndex].Content == d {
if tabs.onInvalidateContent != nil { if tabs.onInvalidateContent != nil {
tabs.onInvalidateContent(tabs.TabContent) tabs.onInvalidateContent(tabs.TabContent)
} }
@ -74,9 +82,9 @@ func (tabs *Tabs) invalidateChild(d Drawable) {
func (tabs *Tabs) Remove(content Drawable) { func (tabs *Tabs) Remove(content Drawable) {
indexToRemove := -1 indexToRemove := -1
for i, tab := range tabs.Tabs { for i, tab := range tabs.tabs {
if tab.Content == content { if tab.Content == content {
tabs.Tabs = append(tabs.Tabs[:i], tabs.Tabs[i+1:]...) tabs.tabs = append(tabs.tabs[:i], tabs.tabs[i+1:]...)
tabs.removeHistory(i) tabs.removeHistory(i)
indexToRemove = i indexToRemove = i
break break
@ -86,20 +94,19 @@ func (tabs *Tabs) Remove(content Drawable) {
return return
} }
// only pop the tab history if the closing tab is selected // only pop the tab history if the closing tab is selected
if indexToRemove == tabs.Selected { if indexToRemove == tabs.curIndex {
index, ok := tabs.popHistory() index, ok := tabs.popHistory()
if ok { if ok {
tabs.Select(index) tabs.selectPriv(index)
interactive, ok := tabs.Tabs[tabs.Selected].Content.(Interactive)
if ok {
interactive.Focus(true)
}
} }
} else if indexToRemove < tabs.Selected { } else if indexToRemove < tabs.curIndex {
// selected tab is now one to the left of where it was // selected tab is now one to the left of where it was
tabs.Selected-- tabs.Select(tabs.curIndex - 1)
}
interactive, ok := tabs.tabs[tabs.curIndex].Content.(Interactive)
if ok {
interactive.Focus(true)
} }
tabs.TabStrip.Invalidate()
} }
func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name string) { func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name string) {
@ -107,9 +114,9 @@ func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name stri
Content: contentTarget, Content: contentTarget,
Name: name, Name: name,
} }
for i, tab := range tabs.Tabs { for i, tab := range tabs.tabs {
if tab.Content == contentSrc { if tab.Content == contentSrc {
tabs.Tabs[i] = replaceTab tabs.tabs[i] = replaceTab
tabs.Select(i) tabs.Select(i)
if c, ok := contentSrc.(io.Closer); ok { if c, ok := contentSrc.(io.Closer); ok {
c.Close() c.Close()
@ -121,20 +128,44 @@ func (tabs *Tabs) Replace(contentSrc Drawable, contentTarget Drawable, name stri
contentTarget.OnInvalidate(tabs.invalidateChild) contentTarget.OnInvalidate(tabs.invalidateChild)
} }
func (tabs *Tabs) Select(index int) { func (tabs *Tabs) Get(index int) *Tab {
if index >= len(tabs.Tabs) { if index < 0 || index >= len(tabs.tabs) {
index = len(tabs.Tabs) - 1 return nil
}
return tabs.tabs[index]
}
func (tabs *Tabs) Selected() *Tab {
if tabs.curIndex < 0 || tabs.curIndex >= len(tabs.tabs) {
return nil
}
return tabs.tabs[tabs.curIndex]
}
func (tabs *Tabs) Select(index int) bool {
if index < 0 || index >= len(tabs.tabs) {
return false
} }
if tabs.Selected != index { if tabs.curIndex != index {
// only push valid tabs onto the history // only push valid tabs onto the history
if tabs.Selected < len(tabs.Tabs) { if tabs.curIndex < len(tabs.tabs) {
tabs.pushHistory(tabs.Selected) tabs.pushHistory(tabs.curIndex)
} }
tabs.Selected = index tabs.curIndex = index
tabs.TabStrip.Invalidate() tabs.TabStrip.Invalidate()
tabs.TabContent.Invalidate() tabs.TabContent.Invalidate()
} }
return true
}
func (tabs *Tabs) SelectName(name string) bool {
for i, tab := range tabs.tabs {
if tab.Name == name {
return tabs.Select(i)
}
}
return false
} }
func (tabs *Tabs) SelectPrevious() bool { func (tabs *Tabs) SelectPrevious() bool {
@ -142,24 +173,25 @@ func (tabs *Tabs) SelectPrevious() bool {
if !ok { if !ok {
return false return false
} }
tabs.Select(index) return tabs.Select(index)
return true
} }
func (tabs *Tabs) MoveTab(to int) { func (tabs *Tabs) MoveTab(to int, relative bool) {
from := tabs.Selected from := tabs.curIndex
if relative {
to = from + to
}
if to < 0 { if to < 0 {
to = 0 to = 0
} }
if to >= len(tabs.tabs) {
if to >= len(tabs.Tabs) { to = len(tabs.tabs) - 1
to = len(tabs.Tabs) - 1
} }
tab := tabs.Tabs[from] tab := tabs.tabs[from]
if to > from { if to > from {
copy(tabs.Tabs[from:to], tabs.Tabs[from+1:to+1]) copy(tabs.tabs[from:to], tabs.tabs[from+1:to+1])
for i, h := range tabs.history { for i, h := range tabs.history {
if h == from { if h == from {
tabs.history[i] = to tabs.history[i] = to
@ -169,7 +201,7 @@ func (tabs *Tabs) MoveTab(to int) {
} }
} }
} else if from > to { } else if from > to {
copy(tabs.Tabs[to+1:from+1], tabs.Tabs[to:from]) copy(tabs.tabs[to+1:from+1], tabs.tabs[to:from])
for i, h := range tabs.history { for i, h := range tabs.history {
if h == from { if h == from {
tabs.history[i] = to tabs.history[i] = to
@ -182,44 +214,44 @@ func (tabs *Tabs) MoveTab(to int) {
return return
} }
tabs.Tabs[to] = tab tabs.tabs[to] = tab
tabs.Selected = to tabs.curIndex = to
tabs.TabStrip.Invalidate() tabs.TabStrip.Invalidate()
} }
func (tabs *Tabs) PinTab() { func (tabs *Tabs) PinTab() {
if tabs.Tabs[tabs.Selected].pinned { if tabs.tabs[tabs.curIndex].pinned {
return return
} }
pinEnd := len(tabs.Tabs) pinEnd := len(tabs.tabs)
for i, t := range tabs.Tabs { for i, t := range tabs.tabs {
if !t.pinned { if !t.pinned {
pinEnd = i pinEnd = i
break break
} }
} }
for _, t := range tabs.Tabs { for _, t := range tabs.tabs {
if t.pinned && t.indexBeforePin > tabs.Selected-pinEnd { if t.pinned && t.indexBeforePin > tabs.curIndex-pinEnd {
t.indexBeforePin -= 1 t.indexBeforePin -= 1
} }
} }
tabs.Tabs[tabs.Selected].pinned = true tabs.tabs[tabs.curIndex].pinned = true
tabs.Tabs[tabs.Selected].indexBeforePin = tabs.Selected - pinEnd tabs.tabs[tabs.curIndex].indexBeforePin = tabs.curIndex - pinEnd
tabs.MoveTab(pinEnd) tabs.MoveTab(pinEnd, false)
} }
func (tabs *Tabs) UnpinTab() { func (tabs *Tabs) UnpinTab() {
if !tabs.Tabs[tabs.Selected].pinned { if !tabs.tabs[tabs.curIndex].pinned {
return return
} }
pinEnd := len(tabs.Tabs) pinEnd := len(tabs.tabs)
for i, t := range tabs.Tabs { for i, t := range tabs.tabs {
if i != tabs.Selected && t.pinned && t.indexBeforePin > tabs.Tabs[tabs.Selected].indexBeforePin { if i != tabs.curIndex && t.pinned && t.indexBeforePin > tabs.tabs[tabs.curIndex].indexBeforePin {
t.indexBeforePin += 1 t.indexBeforePin += 1
} }
if !t.pinned { if !t.pinned {
@ -228,23 +260,23 @@ func (tabs *Tabs) UnpinTab() {
} }
} }
tabs.Tabs[tabs.Selected].pinned = false tabs.tabs[tabs.curIndex].pinned = false
tabs.MoveTab(tabs.Tabs[tabs.Selected].indexBeforePin + pinEnd - 1) tabs.MoveTab(tabs.tabs[tabs.curIndex].indexBeforePin+pinEnd-1, false)
} }
func (tabs *Tabs) NextTab() { func (tabs *Tabs) NextTab() {
next := tabs.Selected + 1 next := tabs.curIndex + 1
if next >= len(tabs.Tabs) { if next >= len(tabs.tabs) {
next = 0 next = 0
} }
tabs.Select(next) tabs.Select(next)
} }
func (tabs *Tabs) PrevTab() { func (tabs *Tabs) PrevTab() {
next := tabs.Selected - 1 next := tabs.curIndex - 1
if next < 0 { if next < 0 {
next = len(tabs.Tabs) - 1 next = len(tabs.tabs) - 1
} }
tabs.Select(next) tabs.Select(next)
} }
@ -284,13 +316,13 @@ func (tabs *Tabs) removeHistory(index int) {
// TODO: Color repository // TODO: Color repository
func (strip *TabStrip) Draw(ctx *Context) { func (strip *TabStrip) Draw(ctx *Context) {
x := 0 x := 0
for i, tab := range strip.Tabs { for i, tab := range strip.tabs {
uiConfig := strip.uiConfig uiConfig := strip.uiConfig
if tab.uiConf != nil { if tab.uiConf != nil {
uiConfig = tab.uiConf uiConfig = tab.uiConf
} }
style := uiConfig.GetStyle(config.STYLE_TAB) style := uiConfig.GetStyle(config.STYLE_TAB)
if strip.Selected == i { if strip.curIndex == i {
style = uiConfig.GetStyleSelected(config.STYLE_TAB) style = uiConfig.GetStyleSelected(config.STYLE_TAB)
} }
tabWidth := 32 tabWidth := 32
@ -319,7 +351,7 @@ func (strip *TabStrip) Invalidate() {
func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) { func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
changeFocus := func(focus bool) { changeFocus := func(focus bool) {
interactive, ok := strip.parent.Tabs[strip.parent.Selected].Content.(Interactive) interactive, ok := strip.parent.tabs[strip.parent.curIndex].Content.(Interactive)
if ok { if ok {
interactive.Focus(focus) interactive.Focus(focus)
} }
@ -330,8 +362,8 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
case *tcell.EventMouse: case *tcell.EventMouse:
switch event.Buttons() { switch event.Buttons() {
case tcell.Button1: case tcell.Button1:
selectedTab, ok := strip.Clicked(localX, localY) selectedTab, ok := strip.clicked(localX, localY)
if !ok || selectedTab == strip.parent.Selected { if !ok || selectedTab == strip.parent.curIndex {
return return
} }
unfocus() unfocus()
@ -346,18 +378,12 @@ func (strip *TabStrip) MouseEvent(localX int, localY int, event tcell.Event) {
strip.parent.PrevTab() strip.parent.PrevTab()
refocus() refocus()
case tcell.Button3: case tcell.Button3:
selectedTab, ok := strip.Clicked(localX, localY) selectedTab, ok := strip.clicked(localX, localY)
if !ok { if !ok {
return return
} }
unfocus() unfocus()
if selectedTab == strip.parent.Selected { strip.parent.CloseTab(selectedTab)
strip.parent.CloseTab(selectedTab)
} else {
current := strip.parent.Selected
strip.parent.CloseTab(selectedTab)
strip.parent.Select(current)
}
refocus() refocus()
} }
} }
@ -367,9 +393,9 @@ func (strip *TabStrip) OnInvalidate(onInvalidate func(d Drawable)) {
strip.onInvalidateStrip = onInvalidate strip.onInvalidateStrip = onInvalidate
} }
func (strip *TabStrip) Clicked(mouseX int, mouseY int) (int, bool) { func (strip *TabStrip) clicked(mouseX int, mouseY int) (int, bool) {
x := 0 x := 0
for i, tab := range strip.Tabs { for i, tab := range strip.tabs {
trunc := runewidth.Truncate(tab.Name, 32, "…") trunc := runewidth.Truncate(tab.Name, 32, "…")
length := len(trunc) + 2 length := len(trunc) + 2
if x <= mouseX && mouseX < x+length { if x <= mouseX && mouseX < x+length {
@ -381,27 +407,27 @@ func (strip *TabStrip) Clicked(mouseX int, mouseY int) (int, bool) {
} }
func (content *TabContent) Children() []Drawable { func (content *TabContent) Children() []Drawable {
children := make([]Drawable, len(content.Tabs)) children := make([]Drawable, len(content.tabs))
for i, tab := range content.Tabs { for i, tab := range content.tabs {
children[i] = tab.Content children[i] = tab.Content
} }
return children return children
} }
func (content *TabContent) Draw(ctx *Context) { func (content *TabContent) Draw(ctx *Context) {
if content.Selected >= len(content.Tabs) { if content.curIndex >= len(content.tabs) {
width := ctx.Width() width := ctx.Width()
height := ctx.Height() height := ctx.Height()
ctx.Fill(0, 0, width, height, ' ', ctx.Fill(0, 0, width, height, ' ',
content.uiConfig.GetStyle(config.STYLE_TAB)) content.uiConfig.GetStyle(config.STYLE_TAB))
} }
tab := content.Tabs[content.Selected] tab := content.tabs[content.curIndex]
tab.Content.Draw(ctx) tab.Content.Draw(ctx)
} }
func (content *TabContent) MouseEvent(localX int, localY int, event tcell.Event) { func (content *TabContent) MouseEvent(localX int, localY int, event tcell.Event) {
tab := content.Tabs[content.Selected] tab := content.tabs[content.curIndex]
switch tabContent := tab.Content.(type) { switch tabContent := tab.Content.(type) {
case Mouseable: case Mouseable:
tabContent.MouseEvent(localX, localY, event) tabContent.MouseEvent(localX, localY, event)
@ -412,7 +438,7 @@ func (content *TabContent) Invalidate() {
if content.onInvalidateContent != nil { if content.onInvalidateContent != nil {
content.onInvalidateContent(content) content.onInvalidateContent(content)
} }
tab := content.Tabs[content.Selected] tab := content.tabs[content.curIndex]
tab.Content.Invalidate() tab.Content.Invalidate()
} }

View File

@ -234,7 +234,7 @@ func (acct *AccountView) SelectedMessagePart() *PartInfo {
} }
func (acct *AccountView) isSelected() bool { func (acct *AccountView) isSelected() bool {
return acct.aerc.NumTabs() > 0 && acct == acct.aerc.SelectedAccount() return acct == acct.aerc.SelectedAccount()
} }
func (acct *AccountView) onMessage(msg types.WorkerMessage) { func (acct *AccountView) onMessage(msg types.WorkerMessage) {

View File

@ -105,8 +105,14 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
aerc.NewTab(wizard, "New account") aerc.NewTab(wizard, "New account")
} }
tabs.Select(0)
tabs.CloseTab = func(index int) { tabs.CloseTab = func(index int) {
switch content := aerc.tabs.Tabs[index].Content.(type) { tab := aerc.tabs.Get(index)
if tab == nil {
return
}
switch content := tab.Content.(type) {
case *AccountView: case *AccountView:
return return
case *AccountWizard: case *AccountWizard:
@ -284,7 +290,7 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
aerc.BeginExCommand("") aerc.BeginExCommand("")
return true return true
} }
interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive) interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
if ok { if ok {
return interactive.Event(event) return interactive.Event(event)
} }
@ -334,18 +340,15 @@ func (aerc *Aerc) SelectedAccountUiConfig() *config.UIConfig {
} }
func (aerc *Aerc) SelectedTabContent() ui.Drawable { func (aerc *Aerc) SelectedTabContent() ui.Drawable {
if aerc.NumTabs() == 0 || aerc.SelectedTabIndex() >= aerc.NumTabs() { tab := aerc.tabs.Selected()
if tab == nil {
return nil return nil
} }
return aerc.tabs.Tabs[aerc.SelectedTabIndex()].Content return tab.Content
} }
func (aerc *Aerc) SelectedTabIndex() int { func (aerc *Aerc) SelectedTab() *ui.Tab {
return aerc.tabs.Selected return aerc.tabs.Selected()
}
func (aerc *Aerc) NumTabs() int {
return len(aerc.tabs.Tabs)
} }
func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab { func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab {
@ -355,7 +358,6 @@ func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab {
uiConf = conf uiConf = conf
} }
tab := aerc.tabs.Add(clickable, name, uiConf) tab := aerc.tabs.Add(clickable, name, uiConf)
aerc.tabs.Select(len(aerc.tabs.Tabs) - 1)
aerc.UpdateStatus() aerc.UpdateStatus()
return tab return tab
} }
@ -369,8 +371,8 @@ func (aerc *Aerc) ReplaceTab(tabSrc ui.Drawable, tabTarget ui.Drawable, name str
aerc.tabs.Replace(tabSrc, tabTarget, name) aerc.tabs.Replace(tabSrc, tabTarget, name)
} }
func (aerc *Aerc) MoveTab(i int) { func (aerc *Aerc) MoveTab(i int, relative bool) {
aerc.tabs.MoveTab(i) aerc.tabs.MoveTab(i, relative)
} }
func (aerc *Aerc) PinTab() { func (aerc *Aerc) PinTab() {
@ -390,33 +392,23 @@ func (aerc *Aerc) PrevTab() {
} }
func (aerc *Aerc) SelectTab(name string) bool { func (aerc *Aerc) SelectTab(name string) bool {
for i, tab := range aerc.tabs.Tabs { ok := aerc.tabs.SelectName(name)
if tab.Name == name { if ok {
aerc.tabs.Select(i) aerc.UpdateStatus()
aerc.UpdateStatus()
return true
}
} }
return false return ok
} }
func (aerc *Aerc) SelectTabIndex(index int) bool { func (aerc *Aerc) SelectTabIndex(index int) bool {
for i := range aerc.tabs.Tabs { ok := aerc.tabs.Select(index)
if i == index { if ok {
aerc.tabs.Select(i) aerc.UpdateStatus()
aerc.UpdateStatus()
return true
}
} }
return false return ok
} }
func (aerc *Aerc) TabNames() []string { func (aerc *Aerc) TabNames() []string {
var names []string return aerc.tabs.Names()
for _, tab := range aerc.tabs.Tabs {
names = append(names, tab.Name)
}
return names
} }
func (aerc *Aerc) SelectPreviousTab() bool { func (aerc *Aerc) SelectPreviousTab() bool {
@ -463,7 +455,7 @@ func (aerc *Aerc) focus(item ui.Interactive) {
aerc.focused.Focus(false) aerc.focused.Focus(false)
} }
aerc.focused = item aerc.focused = item
interactive, ok := aerc.tabs.Tabs[aerc.tabs.Selected].Content.(ui.Interactive) interactive, ok := aerc.SelectedTabContent().(ui.Interactive)
if item != nil { if item != nil {
item.Focus(true) item.Focus(true)
if ok { if ok {