Skip to content

Commit 9bb4327

Browse files
authored
Properly using SpectreConsole's AnsiConsole.Live, creating a simple custom confirmation prompt. (#236)
Fixes #225
1 parent 233411c commit 9bb4327

18 files changed

+320
-400
lines changed

src/winapp-CLI/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
55
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
66
<PackageVersion Include="Spectre.Console.Testing" Version="0.54.0" />
7-
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.264" />
7+
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.269" />
88
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
99
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.2" />
1010
<PackageVersion Include="Microsoft.Telemetry.Inbox.Managed" Version="10.0.25148.1001-220626-1600.rs-fun-deploy-dev5" />

src/winapp-CLI/WinApp.Cli.Tests/BaseCommandTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public abstract class BaseCommandTests(bool configPaths = true, bool verboseLogg
2626

2727
public TestContext TestContext { get; set; } = null!;
2828
private protected TaskContext TestTaskContext { private set; get; } = null!;
29-
private protected LiveUpdateSignal LiveUpdateSignal { private set; get; } = null!;
29+
private protected Lock RenderLock { private set; get; } = null!;
3030
private protected TestConsole TestAnsiConsole { private set; get; } = null!;
3131

3232
[TestInitialize]
@@ -64,8 +64,8 @@ public void SetupBase()
6464

6565
GroupableTask dummyTask = new GroupableTask("Dummy Task", null);
6666

67-
LiveUpdateSignal = new LiveUpdateSignal();
68-
TestTaskContext = new TaskContext(dummyTask, null, TestAnsiConsole, GetRequiredService<ILogger<TaskContext>>(), LiveUpdateSignal);
67+
RenderLock = new Lock();
68+
TestTaskContext = new TaskContext(dummyTask, null, TestAnsiConsole, GetRequiredService<ILogger<TaskContext>>(), RenderLock);
6969

7070
// Set up services with test cache directory
7171
if (configPaths)

src/winapp-CLI/WinApp.Cli.Tests/ManifestUpdateAssetsCommandTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public async Task ManifestUpdateAssetsCommandShouldLogProgress()
232232
// Assert
233233
Assert.AreEqual(0, exitCode, "Update-assets command should complete successfully");
234234

235-
Assert.Contains("Updating manifest assets", TestAnsiConsole.Output, "Should log update message");
235+
Assert.Contains("Updating assets for manifest", TestAnsiConsole.Output, "Should log update message");
236236
Assert.Contains("generated", TestAnsiConsole.Output.ToLowerInvariant(), "Should log generation progress");
237237
}
238238

src/winapp-CLI/WinApp.Cli/Commands/SignCommand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.CommandLine;
55
using System.CommandLine.Invocation;
6-
using WinApp.Cli.Helpers;
76
using WinApp.Cli.Services;
87

98
namespace WinApp.Cli.Commands;

src/winapp-CLI/WinApp.Cli/ConsoleTasks/GroupableTask.cs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ internal class GroupableTask(string inProgressMessage, GroupableTask? parent) :
1717
public bool IsCompleted { get; protected set; }
1818
public GroupableTask? Parent { get; } = parent;
1919
public string InProgressMessage { get; set; } = inProgressMessage;
20+
public bool EscapeInProgressMessage { get; set; } = true;
2021
public string? SubStatus { get; set; }
2122

2223
public void Dispose()
@@ -33,16 +34,16 @@ internal class GroupableTask<T> : GroupableTask
3334
public T? CompletedMessage { get; protected set; }
3435
private readonly Func<TaskContext, CancellationToken, Task<T>>? _taskFunc;
3536
protected readonly IAnsiConsole AnsiConsole;
36-
protected readonly LiveUpdateSignal Signal;
37+
protected readonly Lock RenderLock;
3738
private readonly ILogger _logger;
3839

39-
public GroupableTask(string inProgressMessage, GroupableTask? parent, Func<TaskContext, CancellationToken, Task<T>>? taskFunc, IAnsiConsole ansiConsole, ILogger logger, LiveUpdateSignal signal)
40+
public GroupableTask(string inProgressMessage, GroupableTask? parent, Func<TaskContext, CancellationToken, Task<T>>? taskFunc, IAnsiConsole ansiConsole, ILogger logger, Lock renderLock)
4041
: base(inProgressMessage, parent)
4142
{
4243
_taskFunc = taskFunc;
4344
AnsiConsole = ansiConsole;
4445
_logger = logger;
45-
Signal = signal;
46+
RenderLock = renderLock;
4647
}
4748

4849
public virtual async Task<T?> ExecuteAsync(Action? onUpdate, CancellationToken cancellationToken, bool startSpinner = true)
@@ -53,7 +54,7 @@ public GroupableTask(string inProgressMessage, GroupableTask? parent, Func<TaskC
5354
{
5455
if (_taskFunc != null)
5556
{
56-
var context = new TaskContext(this, onUpdate, AnsiConsole, _logger, Signal);
57+
var context = new TaskContext(this, onUpdate, AnsiConsole, _logger, RenderLock);
5758
CompletedMessage = await _taskFunc(context, cancellationToken);
5859
IsCompleted = true;
5960
}
@@ -72,43 +73,47 @@ public GroupableTask(string inProgressMessage, GroupableTask? parent, Func<TaskC
7273
return CompletedMessage;
7374
}
7475

75-
public (IRenderable, int) Render()
76+
public IRenderable Render()
7677
{
7778
var sb = new StringBuilder();
7879

7980
int maxDepth = _logger.IsEnabled(LogLevel.Debug) ? int.MaxValue : 1;
80-
var lineCount = RenderTask(this, sb, 0, string.Empty, maxDepth);
81+
RenderTask(this, sb, 0, string.Empty, maxDepth);
82+
var allTasksString = sb.ToString().TrimEnd([.. Environment.NewLine]);
83+
if (!allTasksString.Contains(Environment.NewLine))
84+
{
85+
allTasksString = $"{allTasksString}{Environment.NewLine}";
86+
}
8187

82-
var panel = new Panel(new Markup(sb.ToString().TrimEnd([.. Environment.NewLine])))
88+
var panel = new Panel(allTasksString)
8389
{
8490
Border = BoxBorder.None,
8591
Padding = new Padding(0, 0),
8692
Expand = true
8793
};
8894

89-
return (panel, lineCount);
95+
return panel;
9096
}
9197

92-
private static int RenderSubTasks(GroupableTask task, StringBuilder sb, int indentLevel, int maxForcedDepth)
98+
private static void RenderSubTasks(GroupableTask task, StringBuilder sb, int indentLevel, int maxForcedDepth)
9399
{
94100
if (task.SubTasks.Count == 0)
95101
{
96-
return 0;
102+
return;
97103
}
98104

99105
var indentStr = new string(' ', indentLevel * 2);
100-
int lineCount = 0;
101106

102107
foreach (var subTask in task.SubTasks)
103108
{
104-
lineCount += RenderTask(subTask, sb, indentLevel, indentStr, maxForcedDepth);
109+
RenderTask(subTask, sb, indentLevel, indentStr, maxForcedDepth);
105110
}
106-
107-
return lineCount;
108111
}
109112

110-
private static int RenderTask(GroupableTask task, StringBuilder sb, int indentLevel, string indentStr, int maxForcedDepth)
113+
private static void RenderTask(GroupableTask task, StringBuilder sb, int indentLevel, string indentStr, int maxForcedDepth)
111114
{
115+
string msg;
116+
112117
if (task.IsCompleted)
113118
{
114119
static string FormatCheckMarkMessage(string indentStr, string message)
@@ -123,7 +128,8 @@ static string FormatCheckMarkMessage(string indentStr, string message)
123128
}
124129
return firstCharIsEmojiOrOpenBracket ? $"{indentStr} {message}" : $"{indentStr}[green]{Emoji.Known.CheckMarkButton}[/] {message}";
125130
}
126-
sb.AppendLine(task switch
131+
132+
msg = task switch
127133
{
128134
StatusMessageTask statusMessageTask => $"{indentStr} {Markup.Escape(statusMessageTask.CompletedMessage ?? string.Empty)}",
129135
GroupableTask<T> genericTask => FormatCheckMarkMessage(indentStr, (genericTask.CompletedMessage as ITuple) switch
@@ -133,31 +139,38 @@ static string FormatCheckMarkMessage(string indentStr, string message)
133139
_ => genericTask.CompletedMessage?.ToString() ?? string.Empty
134140
}),
135141
GroupableTask _ => FormatCheckMarkMessage(indentStr, Markup.Escape(task.InProgressMessage)),
136-
});
142+
};
137143
}
138144
else
139145
{
140146
var spinnerChars = new[] { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" };
141147
var spinnerIndex = (int)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 100) % spinnerChars.Length;
142148
var spinner = spinnerChars[spinnerIndex];
143149

144-
var msg = task.InProgressMessage;
150+
msg = task.InProgressMessage;
145151
if (!string.IsNullOrEmpty(task.SubStatus))
146152
{
147153
msg = $"{msg} ({task.SubStatus})";
148154
}
149-
150-
sb.AppendLine($"{indentStr}[yellow]{spinner}[/] {Markup.Escape(msg)}");
155+
if (task.EscapeInProgressMessage)
156+
{
157+
msg = Markup.Escape(msg);
158+
}
159+
if (task is not PromptConfirmationTask promptConfirmationTask || promptConfirmationTask.State != PromptState.WaitingForInput)
160+
{
161+
msg = $"{indentStr}[yellow]{spinner}[/] {msg}";
162+
}
151163
}
152164

153-
int lineCount = 1;
165+
// make line endings consistent
166+
msg = msg.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", Environment.NewLine).TrimEnd([.. Environment.NewLine]);
167+
168+
sb.AppendLine(msg);
154169

155170
bool shouldRenderChildren = indentLevel + 1 <= maxForcedDepth || !task.IsCompleted;
156171
if (shouldRenderChildren)
157172
{
158-
lineCount += RenderSubTasks(task, sb, indentLevel + 1, maxForcedDepth);
173+
RenderSubTasks(task, sb, indentLevel + 1, maxForcedDepth);
159174
}
160-
161-
return lineCount;
162175
}
163176
}

src/winapp-CLI/WinApp.Cli/ConsoleTasks/LiveUpdateSignal.cs

Lines changed: 0 additions & 171 deletions
This file was deleted.

0 commit comments

Comments
 (0)