-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathSpectreTargetFrameworkSelectionPrompt.cs
More file actions
101 lines (84 loc) · 3.98 KB
/
SpectreTargetFrameworkSelectionPrompt.cs
File metadata and controls
101 lines (84 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Threading.Channels;
using Spectre.Console;
namespace Microsoft.DotNet.Watch;
internal sealed class SpectreTargetFrameworkSelectionPrompt(IAnsiConsole console) : TargetFrameworkSelectionPrompt
{
public SpectreTargetFrameworkSelectionPrompt(IConsole watchConsole)
: this(CreateConsole(watchConsole))
{
}
public override void Dispose()
=> (console as IDisposable)?.Dispose();
protected override Task<string> PromptAsync(IReadOnlyList<string> targetFrameworks, CancellationToken cancellationToken)
{
var prompt = new SelectionPrompt<string>()
.Title($"[cyan]{Markup.Escape(Resources.SelectTargetFrameworkPrompt)}[/]")
.PageSize(10)
.MoreChoicesText($"[gray]({Markup.Escape(Resources.MoreFrameworksText)})[/]")
.AddChoices(targetFrameworks)
.EnableSearch()
.SearchPlaceholderText(Resources.SearchPlaceholderText);
return prompt.ShowAsync(console, cancellationToken);
}
private static IAnsiConsole CreateConsole(IConsole watchConsole)
{
if (!Console.IsInputRedirected)
{
return AnsiConsole.Console;
}
// When stdin is redirected (e.g. in integration tests), Spectre.Console detects
// non-interactive mode and refuses to prompt. Create a console with forced
// interactivity that reads keys from IConsole.KeyPressed (fed by
// PhysicalConsole.ListenToStandardInputAsync).
var ansiConsole = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Yes,
Interactive = InteractionSupport.Yes,
});
ansiConsole.Profile.Capabilities.Interactive = true;
ansiConsole.Profile.Capabilities.Ansi = true;
return new KeyPressedAnsiConsole(ansiConsole, watchConsole);
}
/// <summary>
/// Wraps an <see cref="IAnsiConsole"/> to read input from <see cref="IConsole.KeyPressed"/> events
/// instead of <see cref="System.Console.ReadKey"/>.
/// </summary>
private sealed class KeyPressedAnsiConsole(IAnsiConsole inner, IConsole watchConsole) : IAnsiConsole, IDisposable
{
private readonly KeyPressedInput _input = new(watchConsole);
public Profile Profile => inner.Profile;
public IAnsiConsoleCursor Cursor => inner.Cursor;
public IAnsiConsoleInput Input => _input;
public IExclusivityMode ExclusivityMode => inner.ExclusivityMode;
public Spectre.Console.Rendering.RenderPipeline Pipeline => inner.Pipeline;
public void Clear(bool home) => inner.Clear(home);
public void Write(Spectre.Console.Rendering.IRenderable renderable) => inner.Write(renderable);
public void Dispose() => _input.Dispose();
}
/// <summary>
/// Bridges <see cref="IConsole.KeyPressed"/> events to Spectre.Console's
/// <see cref="IAnsiConsoleInput"/> using a channel.
/// </summary>
private sealed class KeyPressedInput : IAnsiConsoleInput, IDisposable
{
private readonly Channel<ConsoleKeyInfo> _channel = Channel.CreateUnbounded<ConsoleKeyInfo>();
private readonly IConsole _console;
public KeyPressedInput(IConsole console)
{
_console = console;
_console.KeyPressed += OnKeyPressed;
}
private void OnKeyPressed(ConsoleKeyInfo key)
=> _channel.Writer.TryWrite(key);
public bool IsKeyAvailable()
=> _channel.Reader.TryPeek(out _);
public ConsoleKeyInfo? ReadKey(bool intercept)
=> ReadKeyAsync(intercept, CancellationToken.None).GetAwaiter().GetResult();
public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
=> await _channel.Reader.ReadAsync(cancellationToken);
public void Dispose()
=> _console.KeyPressed -= OnKeyPressed;
}
}