1
0
Files
AnchorCli/SpinnerService.cs
Tomi Eckert 1e943e6566 refactor: apply Single Responsibility Principle to Program.cs and ReplLoop.cs
extracted responsibilities from Program.cs (208→46 lines) and ReplLoop.cs (274→174 lines) into focused service classes: HeaderRenderer, SessionManager, ApplicationStartup, ResponseStreamer, SpinnerService, UsageDisplayer, and ContextCompactionService. Each class now has a single, well-defined responsibility, improving testability and maintainability.
2026-03-11 16:59:06 +01:00

101 lines
2.7 KiB
C#

using Spectre.Console;
namespace AnchorCli;
/// <summary>
/// Manages the "thinking" spinner animation during AI response generation.
/// </summary>
internal sealed class SpinnerService : IDisposable
{
private readonly object _consoleLock = new();
private CancellationTokenSource? _spinnerCts;
private Task? _spinnerTask;
private bool _showSpinner = true;
private bool _disposed;
/// <summary>
/// Starts the spinner animation.
/// </summary>
public void Start(CancellationToken cancellationToken)
{
_spinnerCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_showSpinner = true;
_spinnerTask = Task.Run(async () =>
{
var frames = Spinner.Known.BouncingBar.Frames;
var interval = Spinner.Known.BouncingBar.Interval;
int i = 0;
Console.Write("\x1b[?25l");
try
{
while (!_spinnerCts.Token.IsCancellationRequested)
{
lock (_consoleLock)
{
if (_showSpinner && !_spinnerCts.Token.IsCancellationRequested)
{
var frame = frames[i % frames.Count];
Console.Write($"\r\x1b[38;5;69m{frame}\x1b[0m Thinking...");
i++;
}
}
try { await Task.Delay(interval, _spinnerCts.Token); } catch { }
}
}
finally
{
lock (_consoleLock)
{
if (_showSpinner)
Console.Write("\r" + new string(' ', 40) + "\r");
Console.Write("\x1b[?25h");
}
}
});
}
/// <summary>
/// Stops the spinner animation and waits for it to complete.
/// </summary>
public async Task StopAsync()
{
_spinnerCts?.Cancel();
if (_spinnerTask != null)
{
await Task.WhenAny(_spinnerTask);
}
}
/// <summary>
/// Pauses the spinner (e.g., during tool execution).
/// </summary>
public void Pause()
{
lock (_consoleLock)
{
_showSpinner = false;
Console.Write("\r" + new string(' ', 40) + "\r");
}
}
/// <summary>
/// Resumes the spinner after being paused.
/// </summary>
public void Resume()
{
lock (_consoleLock)
{
_showSpinner = true;
}
}
public void Dispose()
{
if (_disposed) return;
_spinnerCts?.Dispose();
_disposed = true;
}
}