mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 10:35:27 +08:00 
			
		
		
		
	Merge pull request #1747 from phil-scott-78/spinner-extension
This commit is contained in:
		@@ -0,0 +1,92 @@
 | 
			
		||||
namespace Spectre.Console.Extensions;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Provides extension methods for running tasks with a spinner animation.
 | 
			
		||||
/// </summary>
 | 
			
		||||
public static class SpinnerExtensions
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Runs a task with a spinner animation.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="task">The task to run.</param>
 | 
			
		||||
    /// <param name="spinner">The spinner to use.</param>
 | 
			
		||||
    /// <param name="style">The style to apply to the spinner.</param>
 | 
			
		||||
    /// <param name="ansiConsole">The console to write to.</param>
 | 
			
		||||
    /// <returns>The result of the task.</returns>
 | 
			
		||||
    public static async Task Spinner(this Task task, Spinner? spinner = null, Style? style = null, IAnsiConsole? ansiConsole = null)
 | 
			
		||||
    {
 | 
			
		||||
        await SpinnerInternal<object>(task, spinner ?? Console.Spinner.Known.Default, style, ansiConsole);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Runs a task with a spinner animation.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T">The type of the task result.</typeparam>
 | 
			
		||||
    /// <param name="task">The task to run.</param>
 | 
			
		||||
    /// <param name="spinner">The spinner to use.</param>
 | 
			
		||||
    /// <param name="style">The style to apply to the spinner.</param>
 | 
			
		||||
    /// <param name="ansiConsole">The console to write to.</param>
 | 
			
		||||
    /// <returns>The result of the task.</returns>
 | 
			
		||||
    public static async Task<T> Spinner<T>(this Task<T> task, Spinner? spinner = null, Style? style = null, IAnsiConsole? ansiConsole = null)
 | 
			
		||||
    {
 | 
			
		||||
        return (await SpinnerInternal<T>(task, spinner ?? Console.Spinner.Known.Default, 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(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<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(AnsiSequences.CUB(spinnerFrame.Length)));
 | 
			
		||||
            ansiConsole.Cursor.Show();
 | 
			
		||||
            await cancellationTokenSource.CancelAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/Spectre.Console/Internal/Polyfill/CancellationToken.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Spectre.Console/Internal/Polyfill/CancellationToken.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
#if NETSTANDARD2_0
 | 
			
		||||
namespace Spectre.Console
 | 
			
		||||
{
 | 
			
		||||
    internal static class CancellationTokenHelpers
 | 
			
		||||
    {
 | 
			
		||||
        public static Task CancelAsync(this CancellationTokenSource cts)
 | 
			
		||||
        {
 | 
			
		||||
            cts.Cancel();
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
		Reference in New Issue
	
	Block a user