From d921ac6f025ac383aa08b9011d6d08c5b97ce9c8 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Thu, 7 Mar 2024 08:38:59 +0000
Subject: [PATCH] Positioned Progress Tasks - Before or After Other Tasks
(#1250)
---
.../Live/Progress/ProgressContext.cs | 148 ++++++++++++++++--
.../Unit/Live/Progress/ProgressTests.cs | 78 +++++++++
2 files changed, 212 insertions(+), 14 deletions(-)
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",
+ "[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[?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",
+ "[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[1A[2K[?25h",
+ });
+ }
}