Skip to content

Commit 588fe68

Browse files
Fix race conditions in TreeView
1 parent a4a78f1 commit 588fe68

File tree

1 file changed

+81
-2
lines changed

1 file changed

+81
-2
lines changed

treeview.go

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package tview
22

33
import (
4+
"sync"
5+
46
"github.com/gdamore/tcell/v2"
57
)
68

@@ -17,6 +19,9 @@ const (
1719

1820
// TreeNode represents one node in a tree view.
1921
type TreeNode struct {
22+
// Mutex to prevent race conditions.
23+
mu sync.RWMutex
24+
2025
// The reference object.
2126
reference interface{}
2227

@@ -73,6 +78,9 @@ func NewTreeNode(text string) *TreeNode {
7378
// The callback returns whether traversal should continue with the traversed
7479
// node's child nodes (true) or not recurse any deeper (false).
7580
func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
81+
n.mu.Lock()
82+
defer n.mu.Unlock()
83+
7684
n.parent = nil
7785
nodes := []*TreeNode{n}
7886
for len(nodes) > 0 {
@@ -98,46 +106,70 @@ func (n *TreeNode) Walk(callback func(node, parent *TreeNode) bool) *TreeNode {
98106
// will allow you to establish a mapping between the TreeView hierarchy and your
99107
// internal tree structure.
100108
func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
109+
n.mu.Lock()
110+
defer n.mu.Unlock()
111+
101112
n.reference = reference
102113
return n
103114
}
104115

105116
// GetReference returns this node's reference object.
106117
func (n *TreeNode) GetReference() interface{} {
118+
n.mu.RLock()
119+
defer n.mu.RUnlock()
120+
107121
return n.reference
108122
}
109123

110124
// SetChildren sets this node's child nodes.
111125
func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
126+
n.mu.Lock()
127+
defer n.mu.Unlock()
128+
112129
n.children = childNodes
113130
return n
114131
}
115132

116133
// GetText returns this node's text.
117134
func (n *TreeNode) GetText() string {
135+
n.mu.RLock()
136+
defer n.mu.RUnlock()
137+
118138
return n.text
119139
}
120140

121141
// GetChildren returns this node's children.
122142
func (n *TreeNode) GetChildren() []*TreeNode {
143+
n.mu.RLock()
144+
defer n.mu.RUnlock()
145+
123146
return n.children
124147
}
125148

126149
// ClearChildren removes all child nodes from this node.
127150
func (n *TreeNode) ClearChildren() *TreeNode {
151+
n.mu.Lock()
152+
defer n.mu.Unlock()
153+
128154
n.children = nil
129155
return n
130156
}
131157

132158
// AddChild adds a new child node to this node.
133159
func (n *TreeNode) AddChild(node *TreeNode) *TreeNode {
160+
n.mu.Lock()
161+
defer n.mu.Unlock()
162+
134163
n.children = append(n.children, node)
135164
return n
136165
}
137166

138167
// RemoveChild removes a child node from this node. If the child node cannot be
139168
// found, nothing happens.
140169
func (n *TreeNode) RemoveChild(node *TreeNode) *TreeNode {
170+
n.mu.Lock()
171+
defer n.mu.Unlock()
172+
141173
for index, child := range n.children {
142174
if child == node {
143175
n.children = append(n.children[:index], n.children[index+1:]...)
@@ -150,39 +182,54 @@ func (n *TreeNode) RemoveChild(node *TreeNode) *TreeNode {
150182
// SetSelectable sets a flag indicating whether this node can be selected by
151183
// the user.
152184
func (n *TreeNode) SetSelectable(selectable bool) *TreeNode {
185+
n.mu.Lock()
186+
defer n.mu.Unlock()
187+
153188
n.selectable = selectable
154189
return n
155190
}
156191

157192
// SetSelectedFunc sets a function which is called when the user selects this
158193
// node by hitting Enter when it is selected.
159194
func (n *TreeNode) SetSelectedFunc(handler func()) *TreeNode {
195+
n.mu.Lock()
196+
defer n.mu.Unlock()
197+
160198
n.selected = handler
161199
return n
162200
}
163201

164202
// SetExpanded sets whether or not this node's child nodes should be displayed.
165203
func (n *TreeNode) SetExpanded(expanded bool) *TreeNode {
204+
n.mu.Lock()
205+
defer n.mu.Unlock()
206+
166207
n.expanded = expanded
167208
return n
168209
}
169210

170211
// Expand makes the child nodes of this node appear.
171212
func (n *TreeNode) Expand() *TreeNode {
213+
n.mu.Lock()
214+
defer n.mu.Unlock()
215+
172216
n.expanded = true
173217
return n
174218
}
175219

176220
// Collapse makes the child nodes of this node disappear.
177221
func (n *TreeNode) Collapse() *TreeNode {
222+
n.mu.Lock()
223+
defer n.mu.Unlock()
224+
178225
n.expanded = false
179226
return n
180227
}
181228

182229
// ExpandAll expands this node and all descendent nodes.
183230
func (n *TreeNode) ExpandAll() *TreeNode {
184231
n.Walk(func(node, parent *TreeNode) bool {
185-
node.expanded = true
232+
node.Expand()
186233
return true
187234
})
188235
return n
@@ -191,25 +238,34 @@ func (n *TreeNode) ExpandAll() *TreeNode {
191238
// CollapseAll collapses this node and all descendent nodes.
192239
func (n *TreeNode) CollapseAll() *TreeNode {
193240
n.Walk(func(node, parent *TreeNode) bool {
194-
node.expanded = false
241+
node.Collapse()
195242
return true
196243
})
197244
return n
198245
}
199246

200247
// IsExpanded returns whether the child nodes of this node are visible.
201248
func (n *TreeNode) IsExpanded() bool {
249+
n.mu.Lock()
250+
defer n.mu.Unlock()
251+
202252
return n.expanded
203253
}
204254

205255
// SetText sets the node's text which is displayed.
206256
func (n *TreeNode) SetText(text string) *TreeNode {
257+
n.mu.Lock()
258+
defer n.mu.Unlock()
259+
207260
n.text = text
208261
return n
209262
}
210263

211264
// GetColor returns the node's text color.
212265
func (n *TreeNode) GetColor() tcell.Color {
266+
n.mu.RLock()
267+
defer n.mu.RUnlock()
268+
213269
color, _, _ := n.textStyle.Decompose()
214270
return color
215271
}
@@ -218,38 +274,56 @@ func (n *TreeNode) GetColor() tcell.Color {
218274
// sets the background color of the selected text style. For more control over
219275
// styles, use [TreeNode.SetTextStyle] and [TreeNode.SetSelectedTextStyle].
220276
func (n *TreeNode) SetColor(color tcell.Color) *TreeNode {
277+
n.mu.Lock()
278+
defer n.mu.Unlock()
279+
221280
n.textStyle = n.textStyle.Foreground(color)
222281
n.selectedTextStyle = n.selectedTextStyle.Background(color)
223282
return n
224283
}
225284

226285
// SetTextStyle sets the text style for this node.
227286
func (n *TreeNode) SetTextStyle(style tcell.Style) *TreeNode {
287+
n.mu.Lock()
288+
defer n.mu.Unlock()
289+
228290
n.textStyle = style
229291
return n
230292
}
231293

232294
// GetTextStyle returns the text style for this node.
233295
func (n *TreeNode) GetTextStyle() tcell.Style {
296+
n.mu.RLock()
297+
defer n.mu.RUnlock()
298+
234299
return n.textStyle
235300
}
236301

237302
// SetSelectedTextStyle sets the text style for this node when it is selected.
238303
func (n *TreeNode) SetSelectedTextStyle(style tcell.Style) *TreeNode {
304+
n.mu.Lock()
305+
defer n.mu.Unlock()
306+
239307
n.selectedTextStyle = style
240308
return n
241309
}
242310

243311
// GetSelectedTextStyle returns the text style for this node when it is
244312
// selected.
245313
func (n *TreeNode) GetSelectedTextStyle() tcell.Style {
314+
n.mu.RLock()
315+
defer n.mu.RUnlock()
316+
246317
return n.selectedTextStyle
247318
}
248319

249320
// SetIndent sets an additional indentation for this node's text. A value of 0
250321
// keeps the text as far left as possible with a minimum of line graphics. Any
251322
// value greater than that moves the text to the right.
252323
func (n *TreeNode) SetIndent(indent int) *TreeNode {
324+
n.mu.Lock()
325+
defer n.mu.Unlock()
326+
253327
n.indent = indent
254328
return n
255329
}
@@ -259,6 +333,9 @@ func (n *TreeNode) SetIndent(indent int) *TreeNode {
259333
// guaranteed to be up to date immediately after the tree that contains this
260334
// node is drawn.
261335
func (n *TreeNode) GetLevel() int {
336+
n.mu.RLock()
337+
defer n.mu.RUnlock()
338+
262339
return n.level
263340
}
264341

@@ -736,6 +813,7 @@ func (t *TreeView) Draw(screen tcell.Screen) {
736813
continue
737814
}
738815

816+
node.mu.RLock()
739817
// Draw the graphics.
740818
if t.graphics {
741819
// Draw ancestor branches.
@@ -790,6 +868,7 @@ func (t *TreeView) Draw(screen tcell.Screen) {
790868
printWithStyle(screen, node.text, x+node.textX+prefixWidth, posY, 0, width-node.textX-prefixWidth, AlignLeft, style, false)
791869
}
792870
}
871+
defer node.mu.RUnlock()
793872

794873
// Advance.
795874
posY++

0 commit comments

Comments
 (0)