Created
January 28, 2025 04:51
-
-
Save phil-scott-78/0d8a9918a65b6f56677104c6b75539e0 to your computer and use it in GitHub Desktop.
Spectre.Console Spinner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal static class SpinnerExtensions | |
{ | |
public static async Task Spinner(this Task task, Spinner spinner, Style? style = null, IAnsiConsole? ansiConsole = null) | |
{ | |
await SpinnerInternal<object>(task, spinner, style, ansiConsole); | |
} | |
public static async Task<T> Spinner<T>(this Task<T> task, Spinner spinner, Style? style = null, IAnsiConsole? ansiConsole = null) | |
{ | |
return (await SpinnerInternal<T>(task, spinner, style, ansiConsole))!; | |
} | |
private static async Task<T?> SpinnerInternal<T>(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(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<T> 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(CUB(spinnerFrame.Length))); | |
ansiConsole.Cursor.Show(); | |
await cancellationTokenSource.CancelAsync(); | |
} | |
} | |
/// <summary> | |
/// This control function moves the cursor to the left by a specified number of columns. | |
/// The cursor stops at the left border of the page. | |
/// </summary> | |
/// <remarks> | |
/// See <see href="https://vt100.net/docs/vt510-rm/CUB.html"/>. | |
/// </remarks> | |
/// <param name="steps">The number of steps to move backward.</param> | |
/// <returns>The ANSI escape code.</returns> | |
// ReSharper disable once InconsistentNaming | |
private static string CUB(int steps) => $"\e[{steps}D"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
use it like so