-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
170 lines (134 loc) · 5.61 KB
/
Copy pathProgram.cs
File metadata and controls
170 lines (134 loc) · 5.61 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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// InlineSelect — demonstrates using RunnableWrapper<OptionSelector, int?> in inline mode.
//
// NOTE: See https://github.com/gui-cs/clet that turns every Terminal.Gui View into a CLI command
// NOTE: — typed inputs, a real file picker, a Markdown viewer — with consistent JSON output,
// NOTE: predictable exit codes, and full keyboard/mouse support. Works for humans and AI agents alike.
//
// Renders an OptionSelector inline in the terminal with options from the command line.
// Supports horizontal or vertical orientation via --horizontal / --vertical flags.
// Hot keys are auto-assigned from option text.
// Supports --timeout <seconds> to auto-cancel via CancellationToken (demonstrates RunAsync).
// Supports --initial <index> to pre-select an option via IValue.TrySetValueFromString.
//
// Usage:
// dotnet run --project Examples/InlineSelect -- Apple Banana Cherry
// dotnet run --project Examples/InlineSelect -- --horizontal Red Green Blue Yellow
// dotnet run --project Examples/InlineSelect -- --vertical One Two Three
// dotnet run --project Examples/InlineSelect -- --timeout 10 Apple Banana Cherry
// dotnet run --project Examples/InlineSelect -- --initial 1 Apple Banana Cherry
using Terminal.Gui.App;
using Terminal.Gui.Drawing;
using Terminal.Gui.ViewBase;
using Terminal.Gui.Views;
using Timeout = System.Threading.Timeout;
// Check for smoke test mode
var smokeTest = args.Length > 0 && args [0] == "--smoke-test";
if (smokeTest)
{
Application.AppModel = AppModel.Inline;
IApplication smokeApp = Application.Create ().Init ();
using CancellationTokenSource cts = new (TimeSpan.FromSeconds (2));
OptionSelector smokeSelector = new () { Labels = ["A", "B", "C"] };
RunnableWrapper<OptionSelector, int?> smokeWrapper = new (smokeSelector) { Title = "Smoke Test" };
await smokeApp.RunAsync (smokeWrapper, cts.Token);
Console.WriteLine ("Smoke test passed.");
smokeApp.Dispose ();
return 0;
}
// Parse command-line arguments
Orientation orientation = Orientation.Vertical;
List<string> options = [];
int? timeoutSeconds = null;
string? initialValue = null;
for (var i = 0; i < args.Length; i++)
{
var arg = args [i];
switch (arg)
{
case "--horizontal" or "-h": orientation = Orientation.Horizontal; break;
case "--vertical" or "-v": orientation = Orientation.Vertical; break;
case "--timeout" or "-t" when i + 1 < args.Length && int.TryParse (args [i + 1], out var seconds):
timeoutSeconds = seconds;
i++; // skip the next arg (the number)
break;
case "--timeout" or "-t":
Console.Error.WriteLine ("Error: --timeout requires a number of seconds.");
return 1;
case "--initial" or "-i" when i + 1 < args.Length: initialValue = args [++i]; break;
case "--initial" or "-i":
Console.Error.WriteLine ("Error: --initial requires an index value.");
return 1;
default: options.Add (arg); break;
}
}
if (options.Count == 0)
{
Console.Error.WriteLine (
"Usage: InlineSelect [--horizontal|--vertical] [--timeout <seconds>] <option1> <option2> ...");
return 1;
}
// Enable inline mode before Init
Application.AppModel = AppModel.Inline;
IApplication app = Application.Create ().Init ();
// Build the OptionSelector with command-line options
OptionSelector selector = new () { Labels = options, Orientation = orientation, AssignHotKeys = true };
// Wrap in RunnableWrapper — auto-extracts Value via IValue<int?>
RunnableWrapper<OptionSelector, int?> wrapper = new (selector)
{
Title = timeoutSeconds.HasValue
? $"Select an option (Enter to accept, Esc to cancel, {timeoutSeconds}s timeout)"
: "Select an option (Enter to accept, Esc to cancel)",
Width = Dim.Fill (),
BorderStyle = LineStyle.Rounded
};
// Apply initial value if provided — match by label (case-insensitive) or by numeric index
if (initialValue is not null)
{
// First try matching a label
var matchIndex = options.FindIndex (o => string.Equals (o, initialValue, StringComparison.OrdinalIgnoreCase));
if (matchIndex >= 0)
{
selector.Value = matchIndex;
}
else if (!((IValue)selector).TrySetValueFromString (initialValue))
{
Console.Error.WriteLine ($"Error: '{initialValue}' does not match any option and is not a valid index.");
app.Dispose ();
return 1;
}
}
// Run with optional timeout via RunAsync + CancellationToken
if (timeoutSeconds.HasValue)
{
// Use RunAsync with a CancellationToken for timeout-based cancellation
using CancellationTokenSource cts = new (TimeSpan.FromSeconds (timeoutSeconds.Value));
// Show terminal progress indicator counting down the timeout (OSC 9;4)
DateTime startTime = DateTime.UtcNow;
var totalMs = timeoutSeconds.Value * 1000;
await using Timer progressTimer = new (_ => app.Invoke (_ =>
{
var elapsedMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
var percent = Math.Min (elapsedMs * 100 / totalMs, 100);
app.Driver?.ProgressIndicator?.SetValue (percent);
}),
null,
0,
250);
await app.RunAsync (wrapper, cts.Token);
// Clear the progress indicator when done
progressTimer.Change (Timeout.Infinite, Timeout.Infinite);
app.Driver?.ProgressIndicator?.Clear ();
}
else
{
// Run synchronously — blocks until user accepts or cancels
app.Run (wrapper);
}
var result = wrapper.Result;
app.Dispose ();
if (result is { } selectedIndex and >= 0 && selectedIndex < options.Count)
{
Console.WriteLine (options [selectedIndex]);
return 0;
}
return 1;