2026-02-06 08:57:29 -07:00
|
|
|
|
#nullable enable
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.CommandLine;
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Serilog;
|
|
|
|
|
|
using Serilog.Core;
|
|
|
|
|
|
using Serilog.Events;
|
|
|
|
|
|
using Terminal.Gui.App;
|
|
|
|
|
|
using Terminal.Gui.Configuration;
|
|
|
|
|
|
using Terminal.Gui.Drivers;
|
|
|
|
|
|
using UICatalog;
|
|
|
|
|
|
|
|
|
|
|
|
namespace ScenarioRunner;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Command-line tool for running and benchmarking Terminal.Gui scenarios.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static class Program
|
|
|
|
|
|
{
|
|
|
|
|
|
private static LoggingLevelSwitch LogLevelSwitch { get; } = new ();
|
|
|
|
|
|
private const string LOGFILE_LOCATION = "logs";
|
|
|
|
|
|
private static string LogFilePath { get; set; } = string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
public static int Main (string [] args)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.OutputEncoding = Encoding.Default;
|
|
|
|
|
|
|
|
|
|
|
|
// Process command line args
|
|
|
|
|
|
// If no driver is provided, the default driver is used.
|
|
|
|
|
|
// Get allowed driver names
|
|
|
|
|
|
string? [] allowedDrivers = DriverRegistry.GetDriverNames ().ToArray ();
|
|
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Option<string> driverOption = new Option<string> ("--driver") { Description = "The IDriver to use.", DefaultValueFactory = _ => string.Empty };
|
|
|
|
|
|
driverOption.AcceptOnlyFromAmong (allowedDrivers!);
|
|
|
|
|
|
driverOption.Aliases.Add ("-d");
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Option<bool> disableConfigManagement = new ("--disable-cm") { Description = "Indicates Configuration Management should not be enabled." };
|
|
|
|
|
|
disableConfigManagement.Aliases.Add ("-dcm");
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Option<bool> force16Colors = new ("--force-16-colors") { Description = "Forces the driver to use 16-color mode instead of TrueColor." };
|
|
|
|
|
|
force16Colors.Aliases.Add ("-16");
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Option<uint> benchmarkTimeout = new ("--timeout")
|
|
|
|
|
|
{
|
|
|
|
|
|
Description = $"The maximum time in milliseconds to run a benchmark. Default is {Scenario.BenchmarkTimeout}ms.",
|
|
|
|
|
|
DefaultValueFactory = _ => Scenario.BenchmarkTimeout
|
|
|
|
|
|
};
|
|
|
|
|
|
benchmarkTimeout.Aliases.Add ("-t");
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Option<string> resultsFile = new ("--file") { Description = "The file to save benchmark results to." };
|
|
|
|
|
|
resultsFile.Aliases.Add ("-f");
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
|
|
|
|
|
LogFilePath = $"{LOGFILE_LOCATION}/{Assembly.GetExecutingAssembly ().GetName ().Name}";
|
|
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Option<string> debugLogLevel = new Option<string> ("--debug-log-level")
|
|
|
|
|
|
{
|
|
|
|
|
|
Description = "The level to use for logging.", DefaultValueFactory = _ => "Warning"
|
|
|
|
|
|
};
|
|
|
|
|
|
debugLogLevel.AcceptOnlyFromAmong (Enum.GetNames<LogLevel> ());
|
|
|
|
|
|
debugLogLevel.Aliases.Add ("-dl");
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
|
|
|
|
|
// List command
|
|
|
|
|
|
Command listCommand = new ("list", "List all available scenarios");
|
|
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
listCommand.SetAction (_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
ObservableCollection<Scenario> scenarios = Scenario.GetScenarios ();
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Console.WriteLine (@$"Available scenarios ({scenarios.Count})");
|
|
|
|
|
|
Console.WriteLine ();
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
foreach (Scenario s in scenarios)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine (@$" {s.GetName (),-30} {s.GetDescription ()}");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
|
|
|
|
|
// Run command
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Argument<string> scenarioArgument = new ("scenario") { Description = "The name of the Scenario to run." };
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
|
|
|
|
|
Command runCommand = new ("run", "Run a specific scenario") { scenarioArgument };
|
|
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
runCommand.Options.Add (driverOption);
|
|
|
|
|
|
runCommand.Options.Add (disableConfigManagement);
|
|
|
|
|
|
runCommand.Options.Add (force16Colors);
|
|
|
|
|
|
runCommand.Options.Add (debugLogLevel);
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
runCommand.SetAction (parseResult =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// Extract the values using parseResult.
|
|
|
|
|
|
string scenarioName = parseResult.GetRequiredValue (scenarioArgument);
|
|
|
|
|
|
string driver = parseResult.GetRequiredValue (driverOption);
|
|
|
|
|
|
bool disableCm = parseResult.GetRequiredValue (disableConfigManagement);
|
|
|
|
|
|
bool force16 = parseResult.GetRequiredValue (force16Colors);
|
|
|
|
|
|
string logLevel = parseResult.GetRequiredValue (debugLogLevel);
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
// Executing the original logic
|
|
|
|
|
|
SetupLogging (logLevel);
|
Fixes #2381 - Makes terminal attributes work with Default theme (#4745)
* Initial commit
* delted old plan
* Committing progerss
* progress
* Now working.
* Revamp Dark, Light, Green/Amber Phosphor themes to use Color.None for bg
Update Base and Runnable schemes in Dark, Light, Green Phosphor, and
Amber Phosphor themes to use Color.None for backgrounds that previously
matched the Normal background color. This lets the terminal's native
background show through for these themes.
- Dark: Black → None in Normal, HotNormal, Disabled, Editable, ReadOnly
- Light: WhiteSmoke → None in Normal, HotNormal, Disabled, ReadOnly
- Green Phosphor: Black → None in Normal, Active, Highlight (fg stays GreenPhosphor)
- Amber Phosphor: Black → None in Normal, Active, Highlight (fg stays AmberPhosphor)
- Dialog, Menu, Error schemes unchanged (need explicit bg for layering)
- TurboPascal 5, Anders, 8-Bit themes unchanged (specific colors are the point)
- Updated plan doc to reflect completed Parts A/C and new Parts D/E
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Almost ready....
* Rename GetDimColor to GetDimmerColor for clarity
Renamed the Color.GetDimColor method to Color.GetDimmerColor across the entire codebase, including all usages, XML docs, comments, tests, and user-facing documentation. This is a pure rename for clarity—no changes were made to the underlying algorithm or behavior. The new name better reflects the method's purpose (making a color less visually prominent) and improves API consistency. All related test names and documentation references have been updated accordingly.
* Fix HSL scale mismatch in GetBrighterColor/GetDimmerColor
ColorHelper's HSL uses L in range 0-100, not 0-255. The incorrect
/255.0 normalization compressed the lightness range to ~40%, biasing
auto-detection and distorting brightness/dim adjustments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update HexView editingAttribute role selection logic
Changed editingAttribute to use VisualRole.Normal instead of VisualRole.Editable when not read-only. This affects the visual styling or behavior for editable states, ensuring consistency with the intended roles.
* Moved terminal color capability detection from ApplicationImpl.Driver.cs to MainLoopCoordinator.cs for better separation of concerns. MainLoopCoordinator now sets color capabilities and Force16Colors on the driver. Refactored Attribute struct constructors and methods to use expression-bodied members and improved formatting for clarity and reduced boilerplate. Minor code style improvements throughout.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:52:27 -08:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Runner runner = new ();
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
if (!disableCm)
|
|
|
|
|
|
{
|
|
|
|
|
|
runner.SetRuntimeConfig (driver, force16 ? true : null);
|
|
|
|
|
|
ConfigurationManager.Enable (ConfigLocations.All);
|
|
|
|
|
|
}
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Scenario? scenario = FindScenario (scenarioName);
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
if (scenario is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.Error.WriteLine ($"Scenario '{scenarioName}' not found.");
|
|
|
|
|
|
|
|
|
|
|
|
return; // SetAction returns void, so the empty return value works.
|
|
|
|
|
|
}
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
// Pass force16 only if explicitly set (default false means not set)
|
|
|
|
|
|
runner.RunScenario (scenarioName, false);
|
|
|
|
|
|
});
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
|
|
|
|
|
// Benchmark command
|
2026-03-06 01:00:00 +00:00
|
|
|
|
Argument<string?> benchmarkScenarioArgument = new ("scenario")
|
|
|
|
|
|
{
|
|
|
|
|
|
Description = "The name of the Scenario to benchmark. If not specified, all scenarios are benchmarked.", DefaultValueFactory = _ => null
|
|
|
|
|
|
};
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
|
|
|
|
|
Command benchmarkCommand = new ("benchmark", "Benchmark scenarios") { benchmarkScenarioArgument };
|
|
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
benchmarkCommand.Options.Add (driverOption);
|
|
|
|
|
|
benchmarkCommand.Options.Add (disableConfigManagement);
|
|
|
|
|
|
benchmarkCommand.Options.Add (force16Colors);
|
|
|
|
|
|
benchmarkCommand.Options.Add (benchmarkTimeout);
|
|
|
|
|
|
benchmarkCommand.Options.Add (resultsFile);
|
|
|
|
|
|
benchmarkCommand.Options.Add (debugLogLevel);
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
benchmarkCommand.SetAction (parseResult =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// Extract the values using parseResult.
|
|
|
|
|
|
string scenarioName = parseResult.GetRequiredValue (scenarioArgument);
|
|
|
|
|
|
string driver = parseResult.GetRequiredValue (driverOption);
|
|
|
|
|
|
bool disableCm = parseResult.GetRequiredValue (disableConfigManagement);
|
|
|
|
|
|
bool force16 = parseResult.GetRequiredValue (force16Colors);
|
|
|
|
|
|
uint timeout = parseResult.GetRequiredValue (benchmarkTimeout);
|
|
|
|
|
|
string file = parseResult.GetRequiredValue (resultsFile);
|
|
|
|
|
|
string logLevel = parseResult.GetRequiredValue (debugLogLevel);
|
|
|
|
|
|
|
|
|
|
|
|
SetupLogging (logLevel);
|
|
|
|
|
|
Scenario.BenchmarkTimeout = timeout;
|
|
|
|
|
|
|
|
|
|
|
|
Runner runner = new ();
|
|
|
|
|
|
|
|
|
|
|
|
if (!disableCm)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Pass force16 only if explicitly set
|
|
|
|
|
|
runner.SetRuntimeConfig (driver, force16 ? true : null);
|
|
|
|
|
|
ConfigurationManager.Enable (ConfigLocations.All);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
List<BenchmarkResults> results;
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty (scenarioName))
|
|
|
|
|
|
{
|
|
|
|
|
|
// Benchmark all scenarios
|
|
|
|
|
|
ObservableCollection<Scenario> scenarios = Scenario.GetScenarios ();
|
|
|
|
|
|
results = runner.BenchmarkAllScenarios (scenarios);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Benchmark single scenario
|
|
|
|
|
|
Scenario? scenario = FindScenario (scenarioName);
|
|
|
|
|
|
|
|
|
|
|
|
if (scenario is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.Error.WriteLine ($"Scenario '{scenarioName}' not found.");
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BenchmarkResults? result = runner.RunScenario (scenarioName, true);
|
|
|
|
|
|
results = result is { } ? [result] : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (results.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine (@"No benchmark results collected.");
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty (file))
|
|
|
|
|
|
{
|
|
|
|
|
|
Runner.SaveResultsToFile (results, file);
|
|
|
|
|
|
Console.WriteLine (@$"Results saved to {file}");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Display in UI
|
|
|
|
|
|
Runner.DisplayResultsUI (results);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
RootCommand rootCommand = new ("Terminal.Gui Scenario Runner - Run and benchmark Terminal.Gui scenarios") { listCommand, runCommand, benchmarkCommand };
|
2026-02-06 08:57:29 -07:00
|
|
|
|
|
2026-03-06 01:00:00 +00:00
|
|
|
|
return rootCommand.Parse (args).Invoke ();
|
2026-02-06 08:57:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Scenario? FindScenario (string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
ObservableCollection<Scenario> scenarios = Scenario.GetScenarios ();
|
|
|
|
|
|
|
|
|
|
|
|
return scenarios.FirstOrDefault (s => s.GetName ().Equals (name, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void SetupLogging (string logLevelName)
|
|
|
|
|
|
{
|
|
|
|
|
|
var logLevel = Enum.Parse<LogLevel> (logLevelName);
|
|
|
|
|
|
LogLevelSwitch.MinimumLevel = LogLevelToLogEventLevel (logLevel);
|
|
|
|
|
|
|
|
|
|
|
|
Log.Logger = new LoggerConfiguration ().MinimumLevel.ControlledBy (LogLevelSwitch)
|
|
|
|
|
|
.Enrich.FromLogContext ()
|
|
|
|
|
|
.WriteTo.Debug ()
|
|
|
|
|
|
.WriteTo.File (LogFilePath,
|
|
|
|
|
|
rollingInterval: RollingInterval.Day,
|
|
|
|
|
|
outputTemplate:
|
|
|
|
|
|
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
|
|
|
|
|
|
.CreateLogger ();
|
|
|
|
|
|
|
|
|
|
|
|
using ILoggerFactory loggerFactory = LoggerFactory.Create (builder => { builder.AddSerilog (dispose: true).SetMinimumLevel (LogLevel.Trace); });
|
|
|
|
|
|
|
|
|
|
|
|
Logging.Logger = loggerFactory.CreateLogger ("ScenarioRunner");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static LogEventLevel LogLevelToLogEventLevel (LogLevel logLevel) =>
|
|
|
|
|
|
logLevel switch
|
|
|
|
|
|
{
|
|
|
|
|
|
LogLevel.Trace => LogEventLevel.Verbose,
|
|
|
|
|
|
LogLevel.Debug => LogEventLevel.Debug,
|
|
|
|
|
|
LogLevel.Information => LogEventLevel.Information,
|
|
|
|
|
|
LogLevel.Warning => LogEventLevel.Warning,
|
|
|
|
|
|
LogLevel.Error => LogEventLevel.Error,
|
|
|
|
|
|
_ => LogEventLevel.Fatal
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|