mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 18:40:50 +08:00 
			
		
		
		
	
				
					committed by
					
						
						Patrik Svensson
					
				
			
			
				
	
			
			
			
						parent
						
							e280b82679
						
					
				
				
					commit
					1cf30f62fc
				
			@@ -0,0 +1,119 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Spectre.Console.Rendering;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console.Internal
 | 
			
		||||
{
 | 
			
		||||
    internal sealed class DefaultProgressRenderer : ProgressRenderer
 | 
			
		||||
    {
 | 
			
		||||
        private readonly IAnsiConsole _console;
 | 
			
		||||
        private readonly List<ProgressColumn> _columns;
 | 
			
		||||
        private readonly LiveRenderable _live;
 | 
			
		||||
        private readonly object _lock;
 | 
			
		||||
        private readonly Stopwatch _stopwatch;
 | 
			
		||||
        private TimeSpan _lastUpdate;
 | 
			
		||||
 | 
			
		||||
        public override TimeSpan RefreshRate { get; }
 | 
			
		||||
 | 
			
		||||
        public DefaultProgressRenderer(IAnsiConsole console, List<ProgressColumn> columns, TimeSpan refreshRate)
 | 
			
		||||
        {
 | 
			
		||||
            _console = console ?? throw new ArgumentNullException(nameof(console));
 | 
			
		||||
            _columns = columns ?? throw new ArgumentNullException(nameof(columns));
 | 
			
		||||
            _live = new LiveRenderable();
 | 
			
		||||
            _lock = new object();
 | 
			
		||||
            _stopwatch = new Stopwatch();
 | 
			
		||||
            _lastUpdate = TimeSpan.Zero;
 | 
			
		||||
 | 
			
		||||
            RefreshRate = refreshRate;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Started()
 | 
			
		||||
        {
 | 
			
		||||
            _console.Cursor.Hide();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Completed(bool clear)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                if (clear)
 | 
			
		||||
                {
 | 
			
		||||
                    _console.Render(_live.RestoreCursor());
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    _console.WriteLine();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _console.Cursor.Show();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Update(ProgressContext context)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_stopwatch.IsRunning)
 | 
			
		||||
                {
 | 
			
		||||
                    _stopwatch.Start();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var renderContext = new RenderContext(_console.Encoding, _console.Capabilities.LegacyConsole);
 | 
			
		||||
 | 
			
		||||
                var delta = _stopwatch.Elapsed - _lastUpdate;
 | 
			
		||||
                _lastUpdate = _stopwatch.Elapsed;
 | 
			
		||||
 | 
			
		||||
                var grid = new Grid();
 | 
			
		||||
                for (var columnIndex = 0; columnIndex < _columns.Count; columnIndex++)
 | 
			
		||||
                {
 | 
			
		||||
                    var column = new GridColumn().PadRight(1);
 | 
			
		||||
 | 
			
		||||
                    var columnWidth = _columns[columnIndex].GetColumnWidth(renderContext);
 | 
			
		||||
                    if (columnWidth != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        column.Width = columnWidth;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (_columns[columnIndex].NoWrap)
 | 
			
		||||
                    {
 | 
			
		||||
                        column.NoWrap();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Last column?
 | 
			
		||||
                    if (columnIndex == _columns.Count - 1)
 | 
			
		||||
                    {
 | 
			
		||||
                        column.PadRight(0);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    grid.AddColumn(column);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Add rows
 | 
			
		||||
                foreach (var task in context.GetTasks())
 | 
			
		||||
                {
 | 
			
		||||
                    var columns = _columns.Select(column => column.Render(renderContext, task, delta));
 | 
			
		||||
                    grid.AddRow(columns.ToArray());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _live.SetRenderable(new Padder(grid, new Padding(0, 1)));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                yield return _live.PositionCursor();
 | 
			
		||||
 | 
			
		||||
                foreach (var renderable in renderables)
 | 
			
		||||
                {
 | 
			
		||||
                    yield return renderable;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                yield return _live;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,125 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using Spectre.Console.Rendering;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console.Internal
 | 
			
		||||
{
 | 
			
		||||
    internal sealed class FallbackProgressRenderer : ProgressRenderer
 | 
			
		||||
    {
 | 
			
		||||
        private const double FirstMilestone = 25;
 | 
			
		||||
        private static readonly double?[] _milestones = new double?[] { FirstMilestone, 50, 75, 95, 96, 97, 98, 99, 100 };
 | 
			
		||||
 | 
			
		||||
        private readonly Dictionary<int, double> _taskMilestones;
 | 
			
		||||
        private readonly object _lock;
 | 
			
		||||
        private IRenderable? _renderable;
 | 
			
		||||
        private DateTime _lastUpdate;
 | 
			
		||||
 | 
			
		||||
        public override TimeSpan RefreshRate => TimeSpan.FromSeconds(1);
 | 
			
		||||
 | 
			
		||||
        public FallbackProgressRenderer()
 | 
			
		||||
        {
 | 
			
		||||
            _taskMilestones = new Dictionary<int, double>();
 | 
			
		||||
            _lock = new object();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Update(ProgressContext context)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                var hasStartedTasks = false;
 | 
			
		||||
                var updates = new List<(string, double)>();
 | 
			
		||||
 | 
			
		||||
                foreach (var task in context.GetTasks())
 | 
			
		||||
                {
 | 
			
		||||
                    if (!task.IsStarted || task.IsFinished)
 | 
			
		||||
                    {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    hasStartedTasks = true;
 | 
			
		||||
 | 
			
		||||
                    if (TryAdvance(task.Id, task.Percentage))
 | 
			
		||||
                    {
 | 
			
		||||
                        updates.Add((task.Description, task.Percentage));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Got started tasks but no updates for 30 seconds?
 | 
			
		||||
                if (hasStartedTasks && updates.Count == 0 && (DateTime.Now - _lastUpdate) > TimeSpan.FromSeconds(30))
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var task in context.GetTasks())
 | 
			
		||||
                    {
 | 
			
		||||
                        updates.Add((task.Description, task.Percentage));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (updates.Count > 0)
 | 
			
		||||
                {
 | 
			
		||||
                    _lastUpdate = DateTime.Now;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _renderable = BuildTaskGrid(updates);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                var result = new List<IRenderable>();
 | 
			
		||||
                result.AddRange(renderables);
 | 
			
		||||
 | 
			
		||||
                if (_renderable != null)
 | 
			
		||||
                {
 | 
			
		||||
                    result.Add(_renderable);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _renderable = null;
 | 
			
		||||
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool TryAdvance(int task, double percentage)
 | 
			
		||||
        {
 | 
			
		||||
            if (!_taskMilestones.TryGetValue(task, out var milestone))
 | 
			
		||||
            {
 | 
			
		||||
                _taskMilestones.Add(task, FirstMilestone);
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (percentage > milestone)
 | 
			
		||||
            {
 | 
			
		||||
                var nextMilestone = GetNextMilestone(percentage);
 | 
			
		||||
                if (nextMilestone != null && _taskMilestones[task] != nextMilestone)
 | 
			
		||||
                {
 | 
			
		||||
                    _taskMilestones[task] = nextMilestone.Value;
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static double? GetNextMilestone(double percentage)
 | 
			
		||||
        {
 | 
			
		||||
            return Array.Find(_milestones, p => p > percentage);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static IRenderable? BuildTaskGrid(List<(string Name, double Percentage)> updates)
 | 
			
		||||
        {
 | 
			
		||||
            if (updates.Count > 0)
 | 
			
		||||
            {
 | 
			
		||||
                var renderables = new List<IRenderable>();
 | 
			
		||||
                foreach (var (name, percentage) in updates)
 | 
			
		||||
                {
 | 
			
		||||
                    renderables.Add(new Markup($"[blue]{name}[/]: {(int)percentage}%"));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return new Rows(renderables);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,60 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Spectre.Console.Rendering;
 | 
			
		||||
 | 
			
		||||
namespace Spectre.Console.Internal
 | 
			
		||||
{
 | 
			
		||||
    internal sealed class StatusFallbackRenderer : ProgressRenderer
 | 
			
		||||
    {
 | 
			
		||||
        private readonly object _lock;
 | 
			
		||||
        private IRenderable? _renderable;
 | 
			
		||||
        private string? _lastStatus;
 | 
			
		||||
 | 
			
		||||
        public override TimeSpan RefreshRate => TimeSpan.FromMilliseconds(100);
 | 
			
		||||
 | 
			
		||||
        public StatusFallbackRenderer()
 | 
			
		||||
        {
 | 
			
		||||
            _lock = new object();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override void Update(ProgressContext context)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                var task = context.GetTasks().SingleOrDefault();
 | 
			
		||||
                if (task != null)
 | 
			
		||||
                {
 | 
			
		||||
                    // Not same description?
 | 
			
		||||
                    if (_lastStatus != task.Description)
 | 
			
		||||
                    {
 | 
			
		||||
                        _lastStatus = task.Description;
 | 
			
		||||
                        _renderable = new Markup(task.Description + Environment.NewLine);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _renderable = null;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override IEnumerable<IRenderable> Process(RenderContext context, IEnumerable<IRenderable> renderables)
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                var result = new List<IRenderable>();
 | 
			
		||||
                result.AddRange(renderables);
 | 
			
		||||
 | 
			
		||||
                if (_renderable != null)
 | 
			
		||||
                {
 | 
			
		||||
                    result.Add(_renderable);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                _renderable = null;
 | 
			
		||||
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user