namespace Spectre.Console.Extensions;
///
/// Provides extension methods for running tasks with a spinner animation.
///
public static class SpinnerExtensions
{
///
/// Runs a task with a spinner animation.
///
/// The task to run.
/// The spinner to use.
/// The style to apply to the spinner.
/// The console to write to.
/// The result of the task.
public static async Task Spinner(this Task task, Spinner? spinner = null, Style? style = null, IAnsiConsole? ansiConsole = null)
{
await SpinnerInternal(task, spinner ?? Console.Spinner.Known.Default, style, ansiConsole);
}
///
/// Runs a task with a spinner animation.
///
/// The type of the task result.
/// The task to run.
/// The spinner to use.
/// The style to apply to the spinner.
/// The console to write to.
/// The result of the task.
public static async Task Spinner(this Task task, Spinner? spinner = null, Style? style = null, IAnsiConsole? ansiConsole = null)
{
return (await SpinnerInternal(task, spinner ?? Console.Spinner.Known.Default, style, ansiConsole))!;
}
private static async Task SpinnerInternal(Task task, Spinner spinner, Style? style = null, IAnsiConsole? ansiConsole = null)
{
ansiConsole ??= AnsiConsole.Console;
style ??= Style.Plain;
var currentFrame = 0;
var cancellationTokenSource = new CancellationTokenSource();
// Start spinner animation in background
var spinnerTask = Task.Run(
async () =>
{
while (!cancellationTokenSource.Token.IsCancellationRequested)
{
ansiConsole.Cursor.Show(false);
var spinnerFrame = spinner.Frames[currentFrame];
// Write the spinner frame
ansiConsole.Write(new Text(spinnerFrame, style));
ansiConsole.Write(new ControlCode(AnsiSequences.CUB(spinnerFrame.Length)));
currentFrame = (currentFrame + 1) % spinner.Frames.Count;
await Task.Delay(spinner.Interval, cancellationTokenSource.Token);
}
}, cancellationTokenSource.Token);
try
{
// Wait for the actual task to complete
if (task is Task taskWithResult)
{
var result = await taskWithResult;
await cancellationTokenSource.CancelAsync();
await spinnerTask.ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled);
return result;
}
else
{
await task;
await cancellationTokenSource.CancelAsync();
await spinnerTask.ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled);
return default;
}
}
finally
{
var spinnerFrame = spinner.Frames[currentFrame];
ansiConsole.Write(new string(' ', spinnerFrame.Length));
ansiConsole.Write(new ControlCode(AnsiSequences.CUB(spinnerFrame.Length)));
ansiConsole.Cursor.Show();
await cancellationTokenSource.CancelAsync();
}
}
}