@@ -37,12 +37,11 @@ type LoaderConfig struct {
3737 ShineInterval time.Duration
3838 // BaseColor is the base text color (user-defined)
3939 BaseColor RGB
40- // DarkTheme indicates if the terminal is in dark theme (highlighted char is 20% lighter)
41- DarkTheme bool
4240 // Writer is the output writer (default: os.Stdout)
4341 Writer io.Writer
4442 // HideCursor hides the cursor while loading (default: true)
45- HideCursor bool
43+ // Use pointer to distinguish between unset (nil, defaults to true) and explicitly false
44+ HideCursor * bool
4645}
4746
4847// Loader represents a terminal spinner with optional shining text effect
@@ -71,10 +70,11 @@ func NewLoader(cfg LoaderConfig) *Loader {
7170 if cfg .Writer == nil {
7271 cfg .Writer = os .Stdout
7372 }
74- // HideCursor defaults to true (we check if explicitly set to false)
75- // Since bool zero value is false, we need a different approach
76- // For simplicity, we'll always hide cursor by default
77- cfg .HideCursor = true
73+ // HideCursor defaults to true if not explicitly set
74+ if cfg .HideCursor == nil {
75+ hideCursor := true
76+ cfg .HideCursor = & hideCursor
77+ }
7878
7979 return & Loader {
8080 config : cfg ,
@@ -102,7 +102,7 @@ func (l *Loader) Start() {
102102 l .highlightIndex = 0
103103 l .mu .Unlock ()
104104
105- if l .config .HideCursor {
105+ if * l .config .HideCursor {
106106 fmt .Fprint (l .config .Writer , "\033 [?25l" ) // Hide cursor
107107 }
108108
@@ -125,7 +125,7 @@ func (l *Loader) Stop() {
125125 // Clear the line
126126 fmt .Fprint (l .config .Writer , "\r \033 [K" )
127127
128- if l .config .HideCursor {
128+ if * l .config .HideCursor {
129129 fmt .Fprint (l .config .Writer , "\033 [?25h" ) // Show cursor
130130 }
131131}
@@ -167,46 +167,44 @@ func (l *Loader) animate() {
167167 case <- l .stopChan :
168168 return
169169 case <- ticker .C :
170- l .render ()
170+ // Consolidate all shared state access within a single mutex lock
171+ l .mu .Lock ()
172+ symbol := l .config .Symbols [l .symbolIdx ]
173+ text := l .config .Text
174+ highlightIdx := l .highlightIndex
171175
172176 // Update highlight index for shining effect
173177 if l .config .EnableShining {
174- l .mu .Lock ()
175- textLen := len ([]rune (l .config .Text ))
178+ textLen := len ([]rune (text ))
176179 if textLen > 0 {
177180 l .highlightIndex = (l .highlightIndex + 1 ) % textLen
178181 }
179- l .mu .Unlock ()
180182 }
181183
182184 // Update spinner symbol at appropriate interval
183185 spinCounter ++
184186 if spinCounter >= spinThreshold {
185- l .mu .Lock ()
186187 l .symbolIdx = (l .symbolIdx + 1 ) % len (l .config .Symbols )
187- l .mu .Unlock ()
188188 spinCounter = 0
189189 }
190+ l .mu .Unlock ()
191+
192+ // Render outside the lock since it only reads local copies
193+ l .renderWithValues (symbol , text , highlightIdx )
190194 }
191195 }
192196}
193197
194- // render draws the current state to the terminal
195- func (l * Loader ) render () {
196- l .mu .Lock ()
197- symbol := l .config .Symbols [l .symbolIdx ]
198- text := l .config .Text
199- highlightIdx := l .highlightIndex
200- l .mu .Unlock ()
201-
198+ // renderWithValues draws the current state to the terminal with pre-fetched values
199+ func (l * Loader ) renderWithValues (symbol , text string , highlightIdx int ) {
202200 var output strings.Builder
203201 output .WriteString ("\r \033 [K" ) // Clear line first
204202 output .WriteString (symbol )
205203
206204 if text != "" {
207205 output .WriteString (" " )
208206 if l .config .EnableShining {
209- output . WriteString ( l .renderShiningText (text , highlightIdx ) )
207+ l .renderShiningText (& output , text , highlightIdx )
210208 } else {
211209 output .WriteString (text )
212210 }
@@ -215,33 +213,30 @@ func (l *Loader) render() {
215213 fmt .Fprint (l .config .Writer , output .String ())
216214}
217215
218- // renderShiningText renders text with the shining effect
219- func (l * Loader ) renderShiningText (text string , highlightIdx int ) string {
216+ // renderShiningText renders text with the shining effect directly to the builder
217+ func (l * Loader ) renderShiningText (w * strings. Builder , text string , highlightIdx int ) {
220218 runes := []rune (text )
221219 if len (runes ) == 0 {
222- return ""
220+ return
223221 }
224222
225223 baseColor := l .config .BaseColor
226224 highlightColor := lightenColor (baseColor )
227225
228- var result strings.Builder
229226 for i , r := range runes {
230227 if r == ' ' {
231- result .WriteRune (r )
228+ w .WriteRune (r )
232229 continue
233230 }
234231
235232 if i == highlightIdx {
236- result . WriteString ( colorize ( string ( r ), highlightColor ) )
233+ fmt . Fprintf ( w , " \033 [38;2;%d;%d;%dm%c" , highlightColor . R , highlightColor . G , highlightColor . B , r )
237234 } else {
238- result . WriteString ( colorize ( string ( r ), baseColor ) )
235+ fmt . Fprintf ( w , " \033 [38;2;%d;%d;%dm%c" , baseColor . R , baseColor . G , baseColor . B , r )
239236 }
240237 }
241238 // Reset color at the end
242- result .WriteString ("\033 [0m" )
243-
244- return result .String ()
239+ w .WriteString ("\033 [0m" )
245240}
246241
247242// lightenColor returns a color 20% lighter
0 commit comments