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(); } } }