diff --git a/src/Spectre.Console/Live/Progress/ProgressContext.cs b/src/Spectre.Console/Live/Progress/ProgressContext.cs index 4603bc9..6736a35 100644 --- a/src/Spectre.Console/Live/Progress/ProgressContext.cs +++ b/src/Spectre.Console/Live/Progress/ProgressContext.cs @@ -14,7 +14,16 @@ public sealed class ProgressContext /// /// Gets a value indicating whether or not all started tasks have completed. /// - public bool IsFinished => _tasks.Where(x => x.IsStarted).All(task => task.IsFinished); + public bool IsFinished + { + get + { + lock (_taskLock) + { + return _tasks.Where(x => x.IsStarted).All(task => task.IsFinished); + } + } + } internal ProgressContext(IAnsiConsole console, ProgressRenderer renderer) { @@ -33,11 +42,68 @@ public sealed class ProgressContext /// The newly created task. public ProgressTask AddTask(string description, bool autoStart = true, double maxValue = 100) { - return AddTask(description, new ProgressTaskSettings + lock (_taskLock) { - AutoStart = autoStart, - MaxValue = maxValue, - }); + var settings = new ProgressTaskSettings { AutoStart = autoStart, MaxValue = maxValue, }; + + return AddTaskAtInternal(description, settings, _tasks.Count); + } + } + + /// + /// Adds a task. + /// + /// The task description. + /// The index at which the task should be inserted. + /// Whether or not the task should start immediately. + /// The task's max value. + /// The newly created task. + public ProgressTask AddTaskAt(string description, int index, bool autoStart = true, double maxValue = 100) + { + lock (_taskLock) + { + var settings = new ProgressTaskSettings { AutoStart = autoStart, MaxValue = maxValue, }; + + return AddTaskAtInternal(description, settings, index); + } + } + + /// + /// Adds a task before the reference task. + /// + /// The task description. + /// The reference task to add before. + /// Whether or not the task should start immediately. + /// The task's max value. + /// The newly created task. + public ProgressTask AddTaskBefore(string description, ProgressTask referenceProgressTask, bool autoStart = true, double maxValue = 100) + { + lock (_taskLock) + { + var settings = new ProgressTaskSettings { AutoStart = autoStart, MaxValue = maxValue, }; + var indexOfReference = _tasks.IndexOf(referenceProgressTask); + + return AddTaskAtInternal(description, settings, indexOfReference); + } + } + + /// + /// Adds a task after the reference task. + /// + /// The task description. + /// The reference task to add after. + /// Whether or not the task should start immediately. + /// The task's max value. + /// The newly created task. + public ProgressTask AddTaskAfter(string description, ProgressTask referenceProgressTask, bool autoStart = true, double maxValue = 100) + { + lock (_taskLock) + { + var settings = new ProgressTaskSettings { AutoStart = autoStart, MaxValue = maxValue, }; + var indexOfReference = _tasks.IndexOf(referenceProgressTask); + + return AddTaskAtInternal(description, settings, indexOfReference + 1); + } } /// @@ -48,18 +114,58 @@ public sealed class ProgressContext /// The newly created task. public ProgressTask AddTask(string description, ProgressTaskSettings settings) { - if (settings is null) - { - throw new ArgumentNullException(nameof(settings)); - } - lock (_taskLock) { - var task = new ProgressTask(_taskId++, description, settings.MaxValue, settings.AutoStart); + return AddTaskAtInternal(description, settings, _tasks.Count); + } + } - _tasks.Add(task); + /// + /// Adds a task at the specified index. + /// + /// The task description. + /// The task settings. + /// The index at which the task should be inserted. + /// The newly created task. + public ProgressTask AddTaskAt(string description, ProgressTaskSettings settings, int index) + { + lock (_taskLock) + { + return AddTaskAtInternal(description, settings, index); + } + } - return task; + /// + /// Adds a task before the reference task. + /// + /// The task description. + /// The task settings. + /// The reference task to add before. + /// The newly created task. + public ProgressTask AddTaskBefore(string description, ProgressTaskSettings settings, ProgressTask referenceProgressTask) + { + lock (_taskLock) + { + var indexOfReference = _tasks.IndexOf(referenceProgressTask); + + return AddTaskAtInternal(description, settings, indexOfReference); + } + } + + /// + /// Adds a task after the reference task. + /// + /// The task description. + /// The task settings. + /// The reference task to add after. + /// The newly created task. + public ProgressTask AddTaskAfter(string description, ProgressTaskSettings settings, ProgressTask referenceProgressTask) + { + lock (_taskLock) + { + var indexOfReference = _tasks.IndexOf(referenceProgressTask); + + return AddTaskAtInternal(description, settings, indexOfReference + 1); } } @@ -72,6 +178,20 @@ public sealed class ProgressContext _console.Write(new ControlCode(string.Empty)); } + private ProgressTask AddTaskAtInternal(string description, ProgressTaskSettings settings, int position) + { + if (settings is null) + { + throw new ArgumentNullException(nameof(settings)); + } + + var task = new ProgressTask(_taskId++, description, settings.MaxValue, settings.AutoStart); + + _tasks.Insert(position, task); + + return task; + } + internal IReadOnlyList GetTasks() { lock (_taskLock) @@ -79,4 +199,4 @@ public sealed class ProgressContext return new List(_tasks); } } -} \ No newline at end of file +} diff --git a/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressTests.cs b/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressTests.cs index 0f7d44f..65b463b 100644 --- a/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressTests.cs +++ b/test/Spectre.Console.Tests/Unit/Live/Progress/ProgressTests.cs @@ -263,4 +263,82 @@ public sealed class ProgressTests // Then task.RemainingTime.ShouldBe(TimeSpan.MaxValue); } + + [Fact] + public void Should_Render_Tasks_Added_Before_And_After_Correctly() + { + // Given + var console = new TestConsole() + .Width(10) + .Interactive() + .EmitAnsiSequences(); + + var progress = new Progress(console) + .Columns(new TaskDescriptionColumn()) + .AutoRefresh(false) + .AutoClear(true); + + // When + progress.Start(ctx => + { + var foo1 = ctx.AddTask("foo1"); + var foo2 = ctx.AddTask("foo2"); + var foo3 = ctx.AddTask("foo3"); + + var afterFoo1 = ctx.AddTaskAfter("afterFoo1", foo1); + var beforeFoo3 = ctx.AddTaskBefore("beforeFoo3", foo3); + }); + + // Then + console.Output.SplitLines().Select(x => x.Trim()).ToArray() + .ShouldBeEquivalentTo(new[] + { + "[?25l", + "foo1", + "afterFoo1", + "foo2", + "beforeFoo3", + "foo3", + "[?25h", + }); + } + + [Fact] + public void Should_Render_Tasks_At_Specified_Indexes_Correctly() + { + // Given + var console = new TestConsole() + .Width(10) + .Interactive() + .EmitAnsiSequences(); + + var progress = new Progress(console) + .Columns(new TaskDescriptionColumn()) + .AutoRefresh(false) + .AutoClear(true); + + // When + progress.Start(ctx => + { + var foo1 = ctx.AddTask("foo1"); + var foo2 = ctx.AddTask("foo2"); + var foo3 = ctx.AddTask("foo3"); + + var afterFoo1 = ctx.AddTaskAt("afterFoo1", 1); + var beforeFoo3 = ctx.AddTaskAt("beforeFoo3", 3); + }); + + // Then + console.Output.SplitLines().Select(x => x.Trim()).ToArray() + .ShouldBeEquivalentTo(new[] + { + "[?25l", + "foo1", + "afterFoo1", + "foo2", + "beforeFoo3", + "foo3", + "[?25h", + }); + } }