ShowThinkingAnimation inicia ou atualiza a animação "pensando"
(message string)
| 43 | |
| 44 | // ShowThinkingAnimation inicia ou atualiza a animação "pensando" |
| 45 | func (am *AnimationManager) ShowThinkingAnimation(message string) { |
| 46 | am.mu.Lock() |
| 47 | defer am.mu.Unlock() |
| 48 | |
| 49 | // Atualiza a mensagem |
| 50 | am.currentMessage = message |
| 51 | |
| 52 | // If suppressed, store message but don't start the goroutine. Also skip |
| 53 | // when stdout is not a terminal: the carriage-return repaints would be |
| 54 | // noise in a pipe / CI log (the message is still recorded for callers |
| 55 | // that surface it some other way). |
| 56 | if am.suppressed || !theme.ActiveProfile().IsTerminal() { |
| 57 | return |
| 58 | } |
| 59 | |
| 60 | // Se a animação já está rodando, apenas atualize a mensagem e retorne |
| 61 | if am.isRunning { |
| 62 | return |
| 63 | } |
| 64 | |
| 65 | // Inicializa nova animação |
| 66 | am.isRunning = true |
| 67 | am.stopRequested = false |
| 68 | am.done = make(chan struct{}) // Cria um novo canal |
| 69 | |
| 70 | am.wg.Add(1) |
| 71 | go func() { |
| 72 | defer am.wg.Done() |
| 73 | i := 0 |
| 74 | for { |
| 75 | select { |
| 76 | case <-am.done: |
| 77 | // Limpar a linha E resetar cores ANSI |
| 78 | fmt.Print("\r\033[K\033[0m") |
| 79 | _ = os.Stdout.Sync() // Força flush do buffer |
| 80 | return |
| 81 | default: |
| 82 | // Acesso seguro à mensagem atual |
| 83 | am.mu.Lock() |
| 84 | currentMsg := am.currentMessage |
| 85 | am.mu.Unlock() |
| 86 | |
| 87 | // Message tinted with the theme accent (matches reasoning |
| 88 | // cards); the glyph carries the same accent so the spinner |
| 89 | // reads as one themed unit. Reset closes the span so nothing |
| 90 | // bleeds into following output. |
| 91 | // |
| 92 | // The message is clamped to ONE terminal line: the repaint |
| 93 | // protocol (\r + \033[K) only rewinds to the start of the |
| 94 | // current visual line, so a message wider than the terminal |
| 95 | // wraps and leaves a stale copy behind on EVERY tick — ten |
| 96 | // junk lines per second until the tool returns. Queried per |
| 97 | // tick so live terminal resizes are honored. |
| 98 | accent := theme.ANSI(theme.RoleReasoning) |
| 99 | reset := theme.Reset() |
| 100 | glyph := spinnerFrames[i%len(spinnerFrames)] |
| 101 | display := clampSpinnerMessage(currentMsg, terminalCols()) |
| 102 | fmt.Printf("\r\033[K%s%s...%s %s%s%s", accent, display, reset, accent, glyph, reset) |