mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd0eb9f74 | ||
|
|
fa85216554 | ||
|
|
d475e3b30a | ||
|
|
9637066927 | ||
|
|
0b4321115a | ||
|
|
5cd9ece31a | ||
|
|
b0341862cf | ||
|
|
2e7b3d520a | ||
|
|
646f51a628 | ||
|
|
a0bd481255 | ||
|
|
6d197c5140 | ||
|
|
108e56c229 | ||
|
|
66994cd904 | ||
|
|
f9bd936254 | ||
|
|
a068fc68c3 | ||
|
|
aa34c145b9 | ||
|
|
98cf63f485 | ||
|
|
c3286a4842 | ||
|
|
e5bf2bd498 | ||
|
|
5267ebda49 | ||
|
|
f19202b427 | ||
|
|
8e4f33bba4 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -2,9 +2,7 @@ name: Continuous Integration
|
||||
on: pull_request
|
||||
|
||||
env:
|
||||
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
# Disable sending usage data to Microsoft
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
jobs:
|
||||
@@ -28,10 +26,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Git tags'
|
||||
run: git fetch --tags
|
||||
shell: bash
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
|
||||
10
.github/workflows/publish.yaml
vendored
10
.github/workflows/publish.yaml
vendored
@@ -8,9 +8,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
# Disable sending usage data to Microsoft
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
jobs:
|
||||
@@ -39,10 +37,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Git tags'
|
||||
run: git fetch --tags
|
||||
shell: bash
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
@@ -69,10 +63,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Git tags'
|
||||
run: git fetch --tags
|
||||
shell: bash
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# `Spectre.Console`
|
||||
|
||||
_[](https://www.nuget.org/packages/spectre.console)_
|
||||
_[](https://www.nuget.org/packages/spectre.console)_
|
||||
|
||||
A .NET Standard 2.0 library that makes it easier to create beautiful console applications.
|
||||
It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)
|
||||
@@ -50,11 +50,11 @@ like you usually do with the `System.Console` API, but prettier.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Foreground = Color.CornflowerBlue;
|
||||
AnsiConsole.Style = Styles.Underline | Styles.Bold;
|
||||
AnsiConsole.Decoration = Decoration.Underline | Decoration.Bold;
|
||||
AnsiConsole.WriteLine("Hello World!");
|
||||
|
||||
AnsiConsole.Reset();
|
||||
AnsiConsole.MarkupLine("[yellow]{0}[/] [underline]world[/]!", "Goodbye");
|
||||
AnsiConsole.MarkupLine("[bold yellow on red]{0}[/] [underline]world[/]!", "Goodbye");
|
||||
```
|
||||
|
||||
If you want to get a reference to the default `IAnsiConsole`,
|
||||
@@ -66,6 +66,9 @@ Sometimes it's useful to explicitly create a console with specific
|
||||
capabilities, such as during unit testing when you want control
|
||||
over the environment your code runs in.
|
||||
|
||||
It's recommended to not use `AnsiConsole` in code that run as
|
||||
part of a unit test.
|
||||
|
||||
```csharp
|
||||
IAnsiConsole console = AnsiConsole.Create(
|
||||
new AnsiConsoleSettings()
|
||||
|
||||
BIN
gfx/large-logo.png
Normal file
BIN
gfx/large-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 KiB |
BIN
gfx/medium-logo.png
Normal file
BIN
gfx/medium-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
gfx/small-logo.png
Normal file
BIN
gfx/small-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
2
scripts/.gitignore
vendored
Normal file
2
scripts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Generated
|
||||
Temp
|
||||
24
scripts/Generate-Colors.ps1
Normal file
24
scripts/Generate-Colors.ps1
Normal file
@@ -0,0 +1,24 @@
|
||||
##########################################################
|
||||
# Script that generates known colors and lookup tables.
|
||||
##########################################################
|
||||
|
||||
$Output = Join-Path $PSScriptRoot "Temp"
|
||||
$Source = Join-Path $PSScriptRoot "/../src/Spectre.Console"
|
||||
|
||||
if(!(Test-Path $Output -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path $Output | Out-Null
|
||||
}
|
||||
|
||||
# Generate the files
|
||||
Push-Location Generator
|
||||
&dotnet run -- colors "$Output"
|
||||
if(!$?) {
|
||||
Pop-Location
|
||||
Throw "An error occured when generating code."
|
||||
}
|
||||
Pop-Location
|
||||
|
||||
# Copy the files to the correct location
|
||||
Copy-Item (Join-Path "$Output" "Color.Generated.cs") -Destination "$Source/Color.Generated.cs"
|
||||
Copy-Item (Join-Path "$Output" "ColorPalette.Generated.cs") -Destination "$Source/Internal/Colors/ColorPalette.Generated.cs"
|
||||
Copy-Item (Join-Path "$Output" "ColorTable.Generated.cs") -Destination "$Source/Internal/Colors/ColorTable.Generated.cs"
|
||||
59
scripts/Generator/Commands/ColorGeneratorCommand.cs
Normal file
59
scripts/Generator/Commands/ColorGeneratorCommand.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Generator.Models;
|
||||
using Scriban;
|
||||
using Spectre.Cli;
|
||||
using Spectre.IO;
|
||||
|
||||
namespace Generator.Commands
|
||||
{
|
||||
public sealed class ColorGeneratorCommand : Command<GeneratorCommandSettings>
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public ColorGeneratorCommand()
|
||||
{
|
||||
_fileSystem = new FileSystem();
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, GeneratorCommandSettings settings)
|
||||
{
|
||||
var templates = new FilePath[]
|
||||
{
|
||||
"Templates/ColorPalette.Generated.template",
|
||||
"Templates/Color.Generated.template",
|
||||
"Templates/ColorTable.Generated.template"
|
||||
};
|
||||
|
||||
// Read the color model.
|
||||
var model = Color.Parse(File.ReadAllText("Data/colors.json"));
|
||||
|
||||
var output = new DirectoryPath(settings.Output);
|
||||
if (!_fileSystem.Directory.Exists(settings.Output))
|
||||
{
|
||||
_fileSystem.Directory.Create(settings.Output);
|
||||
}
|
||||
|
||||
foreach (var templatePath in templates)
|
||||
{
|
||||
// Parse the Scriban template.
|
||||
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
|
||||
|
||||
// Render the template with the model.
|
||||
var result = template.Render(new { Colors = model });
|
||||
|
||||
// Write output to file
|
||||
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
|
||||
File.WriteAllText(file.FullPath, result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GeneratorCommandSettings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<OUTPUT>")]
|
||||
public string Output { get; set; }
|
||||
}
|
||||
}
|
||||
3842
scripts/Generator/Data/colors.json
Normal file
3842
scripts/Generator/Data/colors.json
Normal file
File diff suppressed because it is too large
Load Diff
36
scripts/Generator/Generator.csproj
Normal file
36
scripts/Generator/Generator.csproj
Normal file
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="out\**" />
|
||||
<EmbeddedResource Remove="out\**" />
|
||||
<None Remove="out\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Data\colors.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\ColorTable.Generated.template">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\Color.Generated.template">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\ColorPalette.Generated.template">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Scriban" Version="2.1.3" />
|
||||
<PackageReference Include="Spectre.Cli" Version="0.36.1-preview.0.6" />
|
||||
<PackageReference Include="Spectre.IO" Version="0.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
37
scripts/Generator/Generator.sln
Normal file
37
scripts/Generator/Generator.sln
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30320.27
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator.csproj", "{5668D267-53E3-4B99-97AE-59AA597D22ED}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5F37FDE3-D591-4D43-8DDE-2ED6BAB0A7B4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
62
scripts/Generator/Models/Color.cs
Normal file
62
scripts/Generator/Models/Color.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Generator.Models
|
||||
{
|
||||
public sealed class Color
|
||||
{
|
||||
public int Number { get; set; }
|
||||
public string Hex { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Rgb Rgb { get; set; }
|
||||
|
||||
public int R => Rgb.R;
|
||||
public int G => Rgb.G;
|
||||
public int B => Rgb.B;
|
||||
|
||||
public static IEnumerable<Color> Parse(string json)
|
||||
{
|
||||
var source = JsonConvert.DeserializeObject<List<Color>>(json);
|
||||
|
||||
var check = new Dictionary<string, Color>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var color in source.OrderBy(c => c.Number))
|
||||
{
|
||||
if (!check.ContainsKey(color.Name))
|
||||
{
|
||||
check.Add(color.Name, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newName = (string)null;
|
||||
for (int i = 1; i < 100; i++)
|
||||
{
|
||||
if (!check.ContainsKey($"{color.Name}_{i}"))
|
||||
{
|
||||
newName = $"{color.Name}_{i}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newName == null)
|
||||
{
|
||||
throw new InvalidOperationException("Impossible!");
|
||||
}
|
||||
|
||||
check.Add(newName, color);
|
||||
color.Name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Rgb
|
||||
{
|
||||
public int R { get; set; }
|
||||
public int G { get; set; }
|
||||
public int B { get; set; }
|
||||
}
|
||||
}
|
||||
14
scripts/Generator/Models/ColorModel.cs
Normal file
14
scripts/Generator/Models/ColorModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Generator.Models
|
||||
{
|
||||
public sealed class ColorModel
|
||||
{
|
||||
public List<Color> Colors { get; set; }
|
||||
|
||||
public ColorModel(IEnumerable<Color> colors)
|
||||
{
|
||||
Colors = new List<Color>(colors);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
scripts/Generator/Models/Palette.cs
Normal file
6
scripts/Generator/Models/Palette.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Generator.Models
|
||||
{
|
||||
public sealed class Palette
|
||||
{
|
||||
}
|
||||
}
|
||||
19
scripts/Generator/Program.cs
Normal file
19
scripts/Generator/Program.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Generator.Commands;
|
||||
using Spectre.Cli;
|
||||
|
||||
namespace Generator
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp();
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.AddCommand<ColorGeneratorCommand>("colors");
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
scripts/Generator/Templates/Color.Generated.template
Normal file
36
scripts/Generator/Templates/Color.Generated.template
Normal file
@@ -0,0 +1,36 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a color.
|
||||
/// </summary>
|
||||
public partial struct Color
|
||||
{
|
||||
internal Color(byte number, byte red, byte green, byte blue, bool isDefault = false)
|
||||
: this(red, green, blue)
|
||||
{
|
||||
Number = number;
|
||||
IsDefault = isDefault;
|
||||
}
|
||||
{{~ for color in colors }}
|
||||
/// <summary>
|
||||
/// Gets the color "{{ color.name }}" (RGB {{ color.r }},{{ color.g }},{{ color.b }}).
|
||||
/// </summary>
|
||||
{{- if string.contains color.name "_" }}
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
|
||||
{{- end}}
|
||||
public static Color {{ color.name }} { get; } = new Color({{ color.number }}, {{ color.r }}, {{ color.g }}, {{ color.b }});
|
||||
{{~ end ~}}
|
||||
}
|
||||
}
|
||||
47
scripts/Generator/Templates/ColorPalette.Generated.template
Normal file
47
scripts/Generator/Templates/ColorPalette.Generated.template
Normal file
@@ -0,0 +1,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
private static List<Color> GenerateLegacyPalette()
|
||||
{
|
||||
return new List<Color>
|
||||
{
|
||||
{{~ for number in 0..7 ~}}
|
||||
Color.{{ colors[number].name }},
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)
|
||||
{
|
||||
return new List<Color>(legacy)
|
||||
{
|
||||
{{~ for number in 8..15 ~}}
|
||||
Color.{{ colors[number].name }},
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)
|
||||
{
|
||||
return new List<Color>(standard)
|
||||
{
|
||||
{{~ for number in 16..255 ~}}
|
||||
Color.{{ colors[number].name }},
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
28
scripts/Generator/Templates/ColorTable.Generated.template
Normal file
28
scripts/Generator/Templates/ColorTable.Generated.template
Normal file
@@ -0,0 +1,28 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static Dictionary<string, int> GenerateTable()
|
||||
{
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{{~ for color in colors ~}}
|
||||
{ "{{ string.downcase color.name }}", {{ color.number }} },
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,3 +75,6 @@ dotnet_diagnostic.CA1032.severity = none
|
||||
|
||||
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
|
||||
dotnet_diagnostic.CA1826.severity = none
|
||||
|
||||
# RCS1079: Throwing of new NotImplementedException.
|
||||
dotnet_diagnostic.RCS1079.severity = warning
|
||||
@@ -18,6 +18,7 @@
|
||||
<Authors>Patrik Svensson</Authors>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl>
|
||||
<PackageIcon>small-logo.png</PackageIcon>
|
||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||
<PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
@@ -9,19 +9,26 @@ namespace Sample
|
||||
{
|
||||
// Use the static API to write some things to the console.
|
||||
AnsiConsole.Foreground = Color.Chartreuse2;
|
||||
AnsiConsole.Style = Styles.Underline | Styles.Bold;
|
||||
AnsiConsole.Decoration = Decoration.Underline | Decoration.Bold;
|
||||
AnsiConsole.WriteLine("Hello World!");
|
||||
AnsiConsole.Reset();
|
||||
AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities);
|
||||
AnsiConsole.WriteLine($"Width={AnsiConsole.Width}, Height={AnsiConsole.Height}");
|
||||
AnsiConsole.MarkupLine("Encoding: [yellow underline]{0}[/]", AnsiConsole.Console.Encoding.EncodingName);
|
||||
AnsiConsole.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height);
|
||||
AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// We can also use System.ConsoleColor with AnsiConsole.
|
||||
// We can also use System.ConsoleColor with AnsiConsole
|
||||
// to set the foreground and background color.
|
||||
foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor)))
|
||||
{
|
||||
AnsiConsole.Foreground = value;
|
||||
AnsiConsole.WriteLine("ConsoleColor.{0}", value);
|
||||
var foreground = value;
|
||||
var background = (ConsoleColor)(15 - (int)value);
|
||||
|
||||
AnsiConsole.Foreground = foreground;
|
||||
AnsiConsole.Background = background;
|
||||
AnsiConsole.WriteLine("{0} on {1}", foreground, background);
|
||||
AnsiConsole.ResetColors();
|
||||
}
|
||||
|
||||
// We can get the default console via the static API.
|
||||
@@ -40,17 +47,123 @@ namespace Sample
|
||||
// and downgrade them to the specified color system.
|
||||
console.WriteLine();
|
||||
console.Foreground = Color.Chartreuse2;
|
||||
console.Style = Styles.Underline | Styles.Bold;
|
||||
console.Decoration = Decoration.Underline | Decoration.Bold;
|
||||
console.WriteLine("Hello World!");
|
||||
console.ResetColors();
|
||||
console.ResetStyle();
|
||||
console.WriteLine("Capabilities: {0}", AnsiConsole.Capabilities);
|
||||
console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height);
|
||||
console.WriteLine("Good bye!");
|
||||
console.ResetDecoration();
|
||||
console.MarkupLine("Capabilities: [yellow underline]{0}[/]", console.Capabilities);
|
||||
console.MarkupLine("Encoding: [yellow underline]{0}[/]", AnsiConsole.Console.Encoding.EncodingName);
|
||||
console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", console.Width, console.Height);
|
||||
console.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
||||
console.WriteLine();
|
||||
|
||||
// Nest some panels and text
|
||||
AnsiConsole.Foreground = Color.Maroon;
|
||||
AnsiConsole.Render(
|
||||
new Panel(
|
||||
new Panel(
|
||||
new Panel(
|
||||
new Panel(
|
||||
Text.New(
|
||||
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
|
||||
"So I put a 📦 in a 📦\nin a 📦 in a 📦\n\n" +
|
||||
"😅", foreground: Color.White))
|
||||
{ Alignment = Justify.Center, Border = BorderKind.Rounded })))
|
||||
{
|
||||
Border = BorderKind.Ascii
|
||||
});
|
||||
|
||||
// Reset colors
|
||||
AnsiConsole.ResetColors();
|
||||
|
||||
// Left adjusted panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
Text.New("Left adjusted\nLeft"))
|
||||
{
|
||||
Expand = true,
|
||||
Alignment = Justify.Left,
|
||||
});
|
||||
|
||||
// Centered panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
Text.New("Centered\nCenter"))
|
||||
{
|
||||
Expand = true,
|
||||
Alignment = Justify.Center,
|
||||
});
|
||||
|
||||
// Right adjusted panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
Text.New("Right adjusted\nRight"))
|
||||
{
|
||||
Expand = true,
|
||||
Alignment = Justify.Right,
|
||||
});
|
||||
|
||||
// A normal, square table
|
||||
var table = new Table();
|
||||
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||
table.AddRow("Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// A rounded table
|
||||
table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||
table.AddRow("Lorem ipsum dolor sit amet, consectetur [blue]adipiscing[/] elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// A rounded table without headers
|
||||
table = new Table { Border = BorderKind.Rounded, ShowHeaders = false };
|
||||
table.AddColumns("[red underline]Foo[/]", "Bar");
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!");
|
||||
table.AddRow("Lorem ipsum dolor sit amet, consectetur [blue]adipiscing[/] elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// Emulate the usage information for "dotnet run"
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]");
|
||||
AnsiConsole.WriteLine();
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 });
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Options:", "", "");
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the MSBuild verbosity level. Allowed values are \nq[grey][[uiet][/], m[grey][[inimal][/], n[grey][[ormal][/], d[grey][[etailed][/], and diag[grey][[nostic][/].");
|
||||
AnsiConsole.Render(grid);
|
||||
|
||||
// A simple table
|
||||
AnsiConsole.WriteLine();
|
||||
table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumn("Foo");
|
||||
table.AddColumn("Bar");
|
||||
table.AddColumn("Baz");
|
||||
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
AnsiConsole.Render(table);
|
||||
|
||||
// Render a table in some panels.
|
||||
AnsiConsole.Render(new Panel(new Panel(table) { Border = BorderKind.Ascii }) { Padding = new Padding(0, 0) });
|
||||
|
||||
// Draw another table
|
||||
table = new Table { Expand = false };
|
||||
table.AddColumn(new TableColumn("Date"));
|
||||
table.AddColumn(new TableColumn("Title"));
|
||||
table.AddColumn(new TableColumn("Production\nBudget"));
|
||||
table.AddColumn(new TableColumn("Box Office"));
|
||||
table.AddRow("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "[red]$375,126,118[/]");
|
||||
table.AddRow("May 25, 2018", "[yellow]Solo[/]: A Star Wars Story", "$275,000,000", "$393,151,347");
|
||||
table.AddRow("Dec 15, 2017", "Star Wars Ep. VIII: The Last Jedi", "$262,000,000", "[bold green]$1,332,539,889[/]");
|
||||
AnsiConsole.Render(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,3 +21,6 @@ dotnet_diagnostic.CA1034.severity = none
|
||||
|
||||
# CA2000: Dispose objects before losing scope
|
||||
dotnet_diagnostic.CA2000.severity = none
|
||||
|
||||
# SA1118: Parameter should not span multiple lines
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(Styles.Bold, "\u001b[1mHello World[0m")]
|
||||
[InlineData(Styles.Dim, "\u001b[2mHello World[0m")]
|
||||
[InlineData(Styles.Italic, "\u001b[3mHello World[0m")]
|
||||
[InlineData(Styles.Underline, "\u001b[4mHello World[0m")]
|
||||
[InlineData(Styles.Invert, "\u001b[7mHello World[0m")]
|
||||
[InlineData(Styles.Conceal, "\u001b[8mHello World[0m")]
|
||||
[InlineData(Styles.SlowBlink, "\u001b[5mHello World[0m")]
|
||||
[InlineData(Styles.RapidBlink, "\u001b[6mHello World[0m")]
|
||||
[InlineData(Styles.Strikethrough, "\u001b[9mHello World[0m")]
|
||||
public void Should_Write_Style_Correctly(Styles style, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Style = style;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(Styles.Bold | Styles.Underline, "\u001b[1;4mHello World[0m")]
|
||||
[InlineData(Styles.Bold | Styles.Underline | Styles.Conceal, "\u001b[1;4;8mHello World[0m")]
|
||||
public void Should_Write_Combined_Styles_Correctly(Styles style, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Style = style;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Spectre.Console.Tests/Extensions/StringExtensions.cs
Normal file
13
src/Spectre.Console.Tests/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string NormalizeLineEndings(this string text)
|
||||
{
|
||||
return text?.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase)
|
||||
?.Replace("\r", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,17 @@ namespace Spectre.Console.Tests
|
||||
|
||||
public string Output => _writer.ToString();
|
||||
|
||||
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes)
|
||||
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
|
||||
{
|
||||
_writer = new StringWriter();
|
||||
|
||||
Console = AnsiConsole.Create(new AnsiConsoleSettings
|
||||
Console = new ConsoleWithWidth(
|
||||
AnsiConsole.Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = ansi,
|
||||
ColorSystem = (ColorSystemSupport)system,
|
||||
Out = _writer,
|
||||
});
|
||||
}), width);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
31
src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs
Normal file
31
src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public sealed class ConsoleWithWidth : IAnsiConsole
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
|
||||
public Capabilities Capabilities => _console.Capabilities;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height => _console.Height;
|
||||
|
||||
public Encoding Encoding => _console.Encoding;
|
||||
|
||||
public Decoration Decoration { get => _console.Decoration; set => _console.Decoration = value; }
|
||||
public Color Foreground { get => _console.Foreground; set => _console.Foreground = value; }
|
||||
public Color Background { get => _console.Background; set => _console.Background = value; }
|
||||
|
||||
public ConsoleWithWidth(IAnsiConsole console, int width)
|
||||
{
|
||||
_console = console;
|
||||
Width = width;
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
_console.Write(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/Spectre.Console.Tests/Fixtures/PlainConsole.cs
Normal file
46
src/Spectre.Console.Tests/Fixtures/PlainConsole.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public sealed class PlainConsole : IAnsiConsole, IDisposable
|
||||
{
|
||||
public Capabilities Capabilities { get; }
|
||||
public Encoding Encoding { get; }
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public Decoration Decoration { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
public Color Background { get; set; }
|
||||
|
||||
public StringWriter Writer { get; }
|
||||
public string Output => Writer.ToString().TrimEnd('\n');
|
||||
public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' });
|
||||
|
||||
public PlainConsole(
|
||||
int width = 80, int height = 9000, Encoding encoding = null,
|
||||
bool supportsAnsi = true, ColorSystem colorSystem = ColorSystem.Standard,
|
||||
bool legacyConsole = false)
|
||||
{
|
||||
Capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole);
|
||||
Encoding = encoding ?? Encoding.UTF8;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Writer = new StringWriter();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Writer.Dispose();
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
Writer.Write(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
47
src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs
Normal file
47
src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(Decoration.Bold, "\u001b[1mHello World[0m")]
|
||||
[InlineData(Decoration.Dim, "\u001b[2mHello World[0m")]
|
||||
[InlineData(Decoration.Italic, "\u001b[3mHello World[0m")]
|
||||
[InlineData(Decoration.Underline, "\u001b[4mHello World[0m")]
|
||||
[InlineData(Decoration.Invert, "\u001b[7mHello World[0m")]
|
||||
[InlineData(Decoration.Conceal, "\u001b[8mHello World[0m")]
|
||||
[InlineData(Decoration.SlowBlink, "\u001b[5mHello World[0m")]
|
||||
[InlineData(Decoration.RapidBlink, "\u001b[6mHello World[0m")]
|
||||
[InlineData(Decoration.Strikethrough, "\u001b[9mHello World[0m")]
|
||||
public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Decoration = decoration;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(Decoration.Bold | Decoration.Underline, "\u001b[1;4mHello World[0m")]
|
||||
[InlineData(Decoration.Bold | Decoration.Underline | Decoration.Conceal, "\u001b[1;4;8mHello World[0m")]
|
||||
public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Decoration = decoration;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,18 @@ using System.Globalization;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Combine_Style_And_Colors()
|
||||
public void Should_Combine_Decoration_And_Colors()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.RoyalBlue1;
|
||||
fixture.Console.Background = Color.NavajoWhite1;
|
||||
fixture.Console.Style = Styles.Italic;
|
||||
fixture.Console.Decoration = Decoration.Italic;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -30,7 +30,7 @@ namespace Spectre.Console.Tests
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.Default;
|
||||
fixture.Console.Background = Color.NavajoWhite1;
|
||||
fixture.Console.Style = Styles.Italic;
|
||||
fixture.Console.Decoration = Decoration.Italic;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -46,7 +46,7 @@ namespace Spectre.Console.Tests
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.RoyalBlue1;
|
||||
fixture.Console.Background = Color.Default;
|
||||
fixture.Console.Style = Styles.Italic;
|
||||
fixture.Console.Decoration = Decoration.Italic;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -56,13 +56,13 @@ namespace Spectre.Console.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Include_Style_If_Set_To_None()
|
||||
public void Should_Not_Include_Decoration_If_Set_To_None()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.RoyalBlue1;
|
||||
fixture.Console.Background = Color.NavajoWhite1;
|
||||
fixture.Console.Style = Styles.None;
|
||||
fixture.Console.Decoration = Decoration.None;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -246,6 +246,38 @@ namespace Spectre.Console.Tests
|
||||
|
||||
public sealed class WriteLine
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Reset_Colors_Correctly_After_Line_Break()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
fixture.Console.Background = ConsoleColor.Red;
|
||||
fixture.Console.WriteLine("Hello");
|
||||
fixture.Console.Background = ConsoleColor.Green;
|
||||
fixture.Console.WriteLine("World");
|
||||
|
||||
// Then
|
||||
fixture.Output.NormalizeLineEndings()
|
||||
.ShouldBe("[101mHello[0m\n[102mWorld[0m\n");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
fixture.Console.Background = ConsoleColor.Red;
|
||||
fixture.Console.WriteLine("Hello\nWorld");
|
||||
|
||||
// Then
|
||||
fixture.Output.NormalizeLineEndings()
|
||||
.ShouldBe("[101mHello[0m\n[101mWorld[0m\n");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(AnsiSupport.Yes)]
|
||||
[InlineData(AnsiSupport.No)]
|
||||
254
src/Spectre.Console.Tests/Unit/ColorTests.cs
Normal file
254
src/Spectre.Console.Tests/Unit/ColorTests.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class ColorTests
|
||||
{
|
||||
public sealed class TheEqualsMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Consider_Color_And_Non_Color_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
|
||||
// When
|
||||
var result = color1.Equals("Foo");
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Consider_Same_Colors_Equal_By_Component()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 0, 128);
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Consider_Same_Known_Colors_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = Color.Cyan1;
|
||||
var color2 = Color.Cyan1;
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Consider_Known_Color_And_Color_With_Same_Components_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = Color.Cyan1;
|
||||
var color2 = new Color(0, 255, 255);
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Consider_Different_Colors_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 128, 128);
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shourd_Not_Consider_Black_And_Default_Colors_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = Color.Default;
|
||||
var color2 = Color.Black;
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheGetHashCodeMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Same_HashCode_For_Same_Colors()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 0, 128);
|
||||
|
||||
// When
|
||||
var hash1 = color1.GetHashCode();
|
||||
var hash2 = color2.GetHashCode();
|
||||
|
||||
// Then
|
||||
hash1.ShouldBe(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Different_HashCode_For_Different_Colors()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 128, 128);
|
||||
|
||||
// When
|
||||
var hash1 = color1.GetHashCode();
|
||||
var hash2 = color2.GetHashCode();
|
||||
|
||||
// Then
|
||||
hash1.ShouldNotBe(hash2);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ImplicitConversions
|
||||
{
|
||||
public sealed class Int32ToColor
|
||||
{
|
||||
public static IEnumerable<object[]> Data =>
|
||||
Enumerable.Range(0, 255)
|
||||
.Select(number => new object[] { number });
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Data))]
|
||||
public void Should_Return_Expected_Color(int number)
|
||||
{
|
||||
// Given, When
|
||||
var result = (Color)number;
|
||||
|
||||
// Then
|
||||
result.ShouldBe(Color.FromInt32(number));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Integer_Is_Lower_Than_Zero()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => (Color)(-1));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Color number must be between 0 and 255");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Integer_Is_Higher_Than_255()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => (Color)256);
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Color number must be between 0 and 255");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConsoleColorToColor
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ConsoleColor.Black, 0)]
|
||||
[InlineData(ConsoleColor.DarkRed, 1)]
|
||||
[InlineData(ConsoleColor.DarkGreen, 2)]
|
||||
[InlineData(ConsoleColor.DarkYellow, 3)]
|
||||
[InlineData(ConsoleColor.DarkBlue, 4)]
|
||||
[InlineData(ConsoleColor.DarkMagenta, 5)]
|
||||
[InlineData(ConsoleColor.DarkCyan, 6)]
|
||||
[InlineData(ConsoleColor.Gray, 7)]
|
||||
[InlineData(ConsoleColor.DarkGray, 8)]
|
||||
[InlineData(ConsoleColor.Red, 9)]
|
||||
[InlineData(ConsoleColor.Green, 10)]
|
||||
[InlineData(ConsoleColor.Yellow, 11)]
|
||||
[InlineData(ConsoleColor.Blue, 12)]
|
||||
[InlineData(ConsoleColor.Magenta, 13)]
|
||||
[InlineData(ConsoleColor.Cyan, 14)]
|
||||
[InlineData(ConsoleColor.White, 15)]
|
||||
public void Should_Return_Expected_Color(ConsoleColor color, int expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = (Color)color;
|
||||
|
||||
// Then
|
||||
result.ShouldBe(Color.FromInt32(expected));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ColorToConsoleColor
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, ConsoleColor.Black)]
|
||||
[InlineData(1, ConsoleColor.DarkRed)]
|
||||
[InlineData(2, ConsoleColor.DarkGreen)]
|
||||
[InlineData(3, ConsoleColor.DarkYellow)]
|
||||
[InlineData(4, ConsoleColor.DarkBlue)]
|
||||
[InlineData(5, ConsoleColor.DarkMagenta)]
|
||||
[InlineData(6, ConsoleColor.DarkCyan)]
|
||||
[InlineData(7, ConsoleColor.Gray)]
|
||||
[InlineData(8, ConsoleColor.DarkGray)]
|
||||
[InlineData(9, ConsoleColor.Red)]
|
||||
[InlineData(10, ConsoleColor.Green)]
|
||||
[InlineData(11, ConsoleColor.Yellow)]
|
||||
[InlineData(12, ConsoleColor.Blue)]
|
||||
[InlineData(13, ConsoleColor.Magenta)]
|
||||
[InlineData(14, ConsoleColor.Cyan)]
|
||||
[InlineData(15, ConsoleColor.White)]
|
||||
public void Should_Return_Expected_ConsoleColor_For_Known_Color(int color, ConsoleColor expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = (ConsoleColor)Color.FromInt32(color);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheToStringMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Color_Name_For_Known_Colors()
|
||||
{
|
||||
// Given, When
|
||||
var name = Color.Fuchsia.ToString();
|
||||
|
||||
// Then
|
||||
name.ShouldBe("fuchsia");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Hex_String_For_Unknown_Colors()
|
||||
{
|
||||
// Given, When
|
||||
var name = new Color(128, 0, 128).ToString();
|
||||
|
||||
// Then
|
||||
name.ShouldBe("#800080 (RGB=128,0,128)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Spectre.Console.Tests/Unit/Composition/BorderTests.cs
Normal file
42
src/Spectre.Console.Tests/Unit/Composition/BorderTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Composition;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
public sealed class BorderTests
|
||||
{
|
||||
public sealed class TheGetBorderMethod
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(BorderKind.None, false, typeof(NoBorder))]
|
||||
[InlineData(BorderKind.Ascii, false, typeof(AsciiBorder))]
|
||||
[InlineData(BorderKind.Square, false, typeof(SquareBorder))]
|
||||
[InlineData(BorderKind.Rounded, false, typeof(RoundedBorder))]
|
||||
[InlineData(BorderKind.None, true, typeof(NoBorder))]
|
||||
[InlineData(BorderKind.Ascii, true, typeof(AsciiBorder))]
|
||||
[InlineData(BorderKind.Square, true, typeof(SquareBorder))]
|
||||
[InlineData(BorderKind.Rounded, true, typeof(SquareBorder))]
|
||||
public void Should_Return_Correct_Border_For_Specified_Kind(BorderKind kind, bool safe, Type expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = Border.GetBorder(kind, safe);
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Unknown_Border_Kind_Is_Specified()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue, false));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Unknown border kind");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/Spectre.Console.Tests/Unit/Composition/GridTests.cs
Normal file
145
src/Spectre.Console.Tests/Unit/Composition/GridTests.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
public sealed class GridTests
|
||||
{
|
||||
public sealed class TheAddRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Rows_Are_Null()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow(null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("columns");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow("Foo"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are less than the number of grid columns.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow("Foo", "Bar"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are greater than the number of grid columns.");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(2);
|
||||
console.Lines[0].ShouldBe("Qux Corgi Waldo");
|
||||
console.Lines[1].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid_Column_Alignment_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Alignment = Justify.Right });
|
||||
grid.AddColumn(new GridColumn { Alignment = Justify.Center });
|
||||
grid.AddColumn(new GridColumn { Alignment = Justify.Left });
|
||||
grid.AddRow("Foo", "Bar", "Baz");
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe(" Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe(" Qux Corgi Waldo");
|
||||
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid_Column_Padding_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(3, 0) });
|
||||
grid.AddColumns(2);
|
||||
grid.AddRow("Foo", "Bar", "Baz");
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe(" Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe(" Qux Corgi Waldo");
|
||||
console.Lines[2].ShouldBe(" Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid()
|
||||
{
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) });
|
||||
grid.AddRow("[bold]Options[/]", string.Empty);
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(4);
|
||||
console.Lines[0].ShouldBe("Options ");
|
||||
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
||||
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
||||
console.Lines[3].ShouldBe(" The default for most projects is Debug.");
|
||||
}
|
||||
}
|
||||
}
|
||||
180
src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs
Normal file
180
src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class PanelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Panel()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New("Hello World")));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌─────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└─────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_With_Padding()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New("Hello World"))
|
||||
{
|
||||
Padding = new Padding(3, 5),
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌───────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└───────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_With_Unicode_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New(" \n💩\n ")));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(5);
|
||||
console.Lines[0].ShouldBe("┌────┐");
|
||||
console.Lines[1].ShouldBe("│ │");
|
||||
console.Lines[2].ShouldBe("│ 💩 │");
|
||||
console.Lines[3].ShouldBe("│ │");
|
||||
console.Lines[4].ShouldBe("└────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_With_Multiple_Lines()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New("Hello World\nFoo Bar")));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(4);
|
||||
console.Lines[0].ShouldBe("┌─────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("│ Foo Bar │");
|
||||
console.Lines[3].ShouldBe("└─────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Preserve_Explicit_Line_Ending()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var text = new Panel(
|
||||
Text.New("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"));
|
||||
|
||||
// When
|
||||
console.Render(text);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ I heard you like 📦 │");
|
||||
console.Lines[2].ShouldBe("│ │");
|
||||
console.Lines[3].ShouldBe("│ │");
|
||||
console.Lines[4].ShouldBe("│ │");
|
||||
console.Lines[5].ShouldBe("│ So I put a 📦 in a 📦 │");
|
||||
console.Lines[6].ShouldBe("└───────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Expand_Panel_If_Enabled()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New("Hello World"))
|
||||
{
|
||||
Expand = true,
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].Length.ShouldBe(80);
|
||||
console.Lines[0].ShouldBe("┌──────────────────────────────────────────────────────────────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Justify_Child_To_Right()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 25);
|
||||
|
||||
// When
|
||||
console.Render(
|
||||
new Panel(
|
||||
Text.New("Hello World").WithAlignment(Justify.Right))
|
||||
{
|
||||
Expand = true,
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└───────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Justify_Child_To_Center()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 25);
|
||||
|
||||
// When
|
||||
console.Render(
|
||||
new Panel(
|
||||
Text.New("Hello World").WithAlignment(Justify.Center))
|
||||
{
|
||||
Expand = true,
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└───────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_Inside_Panel_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(new Panel(Text.New("Hello World"))));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(5);
|
||||
console.Lines[0].ShouldBe("┌─────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ ┌─────────────┐ │");
|
||||
console.Lines[2].ShouldBe("│ │ Hello World │ │");
|
||||
console.Lines[3].ShouldBe("│ └─────────────┘ │");
|
||||
console.Lines[4].ShouldBe("└─────────────────┘");
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/Spectre.Console.Tests/Unit/Composition/SegmentTests.cs
Normal file
92
src/Spectre.Console.Tests/Unit/Composition/SegmentTests.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Shouldly;
|
||||
using Spectre.Console.Composition;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class SegmentTests
|
||||
{
|
||||
public sealed class TheSplitMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Split_Segment_Correctly()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
|
||||
var segment = new Segment("Foo Bar", style);
|
||||
|
||||
// When
|
||||
var (first, second) = segment.Split(3);
|
||||
|
||||
// Then
|
||||
first.Text.ShouldBe("Foo");
|
||||
first.Style.ShouldBe(style);
|
||||
second.Text.ShouldBe(" Bar");
|
||||
second.Style.ShouldBe(style);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheSplitLinesMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Split_Segment()
|
||||
{
|
||||
var lines = Segment.SplitLines(
|
||||
new[]
|
||||
{
|
||||
new Segment("Foo"),
|
||||
new Segment("Bar"),
|
||||
new Segment("\n"),
|
||||
new Segment("Baz"),
|
||||
new Segment("Qux"),
|
||||
new Segment("\n"),
|
||||
new Segment("Corgi"),
|
||||
});
|
||||
|
||||
// Then
|
||||
lines.Count.ShouldBe(3);
|
||||
|
||||
lines[0].Count.ShouldBe(2);
|
||||
lines[0][0].Text.ShouldBe("Foo");
|
||||
lines[0][1].Text.ShouldBe("Bar");
|
||||
|
||||
lines[1].Count.ShouldBe(2);
|
||||
lines[1][0].Text.ShouldBe("Baz");
|
||||
lines[1][1].Text.ShouldBe("Qux");
|
||||
|
||||
lines[2].Count.ShouldBe(1);
|
||||
lines[2][0].Text.ShouldBe("Corgi");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Split_Segments_With_Linebreak_In_Text()
|
||||
{
|
||||
var lines = Segment.SplitLines(
|
||||
new[]
|
||||
{
|
||||
new Segment("Foo\n"),
|
||||
new Segment("Bar\n"),
|
||||
new Segment("Baz"),
|
||||
new Segment("Qux\n"),
|
||||
new Segment("Corgi"),
|
||||
});
|
||||
|
||||
// Then
|
||||
lines.Count.ShouldBe(4);
|
||||
|
||||
lines[0].Count.ShouldBe(1);
|
||||
lines[0][0].Text.ShouldBe("Foo");
|
||||
|
||||
lines[1].Count.ShouldBe(1);
|
||||
lines[1][0].Text.ShouldBe("Bar");
|
||||
|
||||
lines[2].Count.ShouldBe(2);
|
||||
lines[2][0].Text.ShouldBe("Baz");
|
||||
lines[2][1].Text.ShouldBe("Qux");
|
||||
|
||||
lines[3].Count.ShouldBe(1);
|
||||
lines[3][0].Text.ShouldBe("Corgi");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
311
src/Spectre.Console.Tests/Unit/Composition/TableTests.cs
Normal file
311
src/Spectre.Console.Tests/Unit/Composition/TableTests.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit.Composition
|
||||
{
|
||||
public sealed class TableTests
|
||||
{
|
||||
public sealed class TheAddColumnMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Column_Is_Null()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddColumn((string)null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("column");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddColumnsMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Columns_Are_Null()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddColumns((string[])null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("columns");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Rows_Are_Null()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddRow(null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("columns");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
table.AddColumn("Hello");
|
||||
table.AddColumn("World");
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddRow("Foo"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are less than the number of table columns.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
table.AddColumn("Hello");
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddRow("Foo", "Bar"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are greater than the number of table columns.");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("└────────┴────────┴───────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Nested_In_Panels_Correctly()
|
||||
{
|
||||
// A simple table
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table() { Border = BorderKind.Rounded };
|
||||
table.AddColumn("Foo");
|
||||
table.AddColumn("Bar");
|
||||
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right });
|
||||
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// Render a table in some panels.
|
||||
console.Render(new Panel(new Panel(table)
|
||||
{
|
||||
Border = BorderKind.Ascii,
|
||||
}));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(11);
|
||||
console.Lines[00].ShouldBe("┌───────────────────────────────────┐");
|
||||
console.Lines[01].ShouldBe("│ +-------------------------------+ │");
|
||||
console.Lines[02].ShouldBe("│ | ╭──────────┬────────┬───────╮ | │");
|
||||
console.Lines[03].ShouldBe("│ | │ Foo │ Bar │ Baz │ | │");
|
||||
console.Lines[04].ShouldBe("│ | ├──────────┼────────┼───────┤ | │");
|
||||
console.Lines[05].ShouldBe("│ | │ Qux │ Corgi │ Waldo │ | │");
|
||||
console.Lines[06].ShouldBe("│ | │ Quuuuuux │ │ │ | │");
|
||||
console.Lines[07].ShouldBe("│ | │ Grault │ Garply │ Fred │ | │");
|
||||
console.Lines[08].ShouldBe("│ | ╰──────────┴────────┴───────╯ | │");
|
||||
console.Lines[09].ShouldBe("│ +-------------------------------+ │");
|
||||
console.Lines[10].ShouldBe("└───────────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Column_Justification_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumn(new TableColumn("Foo") { Alignment = Justify.Left });
|
||||
table.AddColumn(new TableColumn("Bar") { Alignment = Justify.Right });
|
||||
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Center });
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Lorem ipsum dolor sit amet");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬────────────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼────────────────────────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Lorem ipsum dolor sit amet │");
|
||||
console.Lines[5].ShouldBe("└────────┴────────┴────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Expand_Table_To_Available_Space_If_Specified()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table() { Expand = true };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].Length.ShouldBe(80);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────────┬───────────────────────────┬──────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├───────────────────────────┼───────────────────────────┼──────────────────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Ascii_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.Ascii };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("+-------------------------+");
|
||||
console.Lines[1].ShouldBe("| Foo | Bar | Baz |");
|
||||
console.Lines[2].ShouldBe("|--------+--------+-------|");
|
||||
console.Lines[3].ShouldBe("| Qux | Corgi | Waldo |");
|
||||
console.Lines[4].ShouldBe("| Grault | Garply | Fred |");
|
||||
console.Lines[5].ShouldBe("+-------------------------+");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Rounded_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("╭────────┬────────┬───────╮");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("╰────────┴────────┴───────╯");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_No_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.None };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe("Qux Corgi Waldo");
|
||||
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Quuux │ │ │");
|
||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Cell_Padding_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar");
|
||||
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) });
|
||||
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬──────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼──────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Quuux │ │ │");
|
||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[6].ShouldBe("└────────┴────────┴──────────┘");
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/Spectre.Console.Tests/Unit/Composition/TextTests.cs
Normal file
77
src/Spectre.Console.Tests/Unit/Composition/TextTests.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class TextTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Unstyled_Text_As_Expected()
|
||||
{
|
||||
// Given
|
||||
var fixture = new PlainConsole(width: 80);
|
||||
var text = Text.New("Hello World");
|
||||
|
||||
// When
|
||||
fixture.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe("Hello World");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width()
|
||||
{
|
||||
// Given
|
||||
var fixture = new PlainConsole(width: 5);
|
||||
var text = Text.New("Hello World");
|
||||
|
||||
// When
|
||||
fixture.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe("Hello\n Worl\nd");
|
||||
}
|
||||
|
||||
public sealed class TheStylizeMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Apply_Style_To_Text()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
var text = Text.New("Hello World");
|
||||
text.Stylize(start: 3, end: 8, new Style(decoration: Decoration.Underline));
|
||||
|
||||
// When
|
||||
fixture.Console.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe("Hel[4mlo Wo[0mrld");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Apply_Style_To_Text_Which_Spans_Over_Multiple_Lines()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, width: 5);
|
||||
var text = Text.New("Hello World");
|
||||
text.Stylize(start: 3, end: 8, new Style(decoration: Decoration.Underline));
|
||||
|
||||
// When
|
||||
fixture.Console.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe("Hel[4mlo[0m\n[4m Wo[0mrl\nd");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
237
src/Spectre.Console.Tests/Unit/StyleTests.cs
Normal file
237
src/Spectre.Console.Tests/Unit/StyleTests.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class StyleTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Combine_Two_Styles_As_Expected()
|
||||
{
|
||||
// Given
|
||||
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic);
|
||||
var other = new Style(Color.Green, Color.Silver, Decoration.Underline);
|
||||
|
||||
// When
|
||||
var result = first.Combine(other);
|
||||
|
||||
// Then
|
||||
result.Foreground.ShouldBe(Color.Green);
|
||||
result.Background.ShouldBe(Color.Silver);
|
||||
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline);
|
||||
}
|
||||
|
||||
public sealed class TheParseMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Default_Keyword_Should_Return_Default_Style()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.Parse("default");
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeNull();
|
||||
result.Foreground.ShouldBe(Color.Default);
|
||||
result.Background.ShouldBe(Color.Default);
|
||||
result.Decoration.ShouldBe(Decoration.None);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("bold", Decoration.Bold)]
|
||||
[InlineData("dim", Decoration.Dim)]
|
||||
[InlineData("italic", Decoration.Italic)]
|
||||
[InlineData("underline", Decoration.Underline)]
|
||||
[InlineData("invert", Decoration.Invert)]
|
||||
[InlineData("conceal", Decoration.Conceal)]
|
||||
[InlineData("slowblink", Decoration.SlowBlink)]
|
||||
[InlineData("rapidblink", Decoration.RapidBlink)]
|
||||
[InlineData("strikethrough", Decoration.Strikethrough)]
|
||||
public void Should_Parse_Decoration(string text, Decoration decoration)
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.Parse(text);
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeNull();
|
||||
result.Decoration.ShouldBe(decoration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Text_And_Decoration()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.Parse("bold underline blue on green");
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeNull();
|
||||
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
|
||||
result.Foreground.ShouldBe(Color.Blue);
|
||||
result.Background.ShouldBe(Color.Green);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Background_If_Foreground_Is_Set_To_Default()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.Parse("default on green");
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeNull();
|
||||
result.Decoration.ShouldBe(Decoration.None);
|
||||
result.Foreground.ShouldBe(Color.Default);
|
||||
result.Background.ShouldBe(Color.Green);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Foreground_Is_Set_Twice()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Style.Parse("green yellow"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("A foreground color has already been set.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Background_Is_Set_Twice()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Style.Parse("green on blue yellow"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("A background color has already been set.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Style.Parse("bold lol"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Could not find color or style 'lol'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Style.Parse("blue on lol"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Could not find color 'lol'.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheTryParseMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Default_Keyword_Should_Return_Default_Style()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("default", out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
style.ShouldNotBeNull();
|
||||
style.Foreground.ShouldBe(Color.Default);
|
||||
style.Background.ShouldBe(Color.Default);
|
||||
style.Decoration.ShouldBe(Decoration.None);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("bold", Decoration.Bold)]
|
||||
[InlineData("dim", Decoration.Dim)]
|
||||
[InlineData("italic", Decoration.Italic)]
|
||||
[InlineData("underline", Decoration.Underline)]
|
||||
[InlineData("invert", Decoration.Invert)]
|
||||
[InlineData("conceal", Decoration.Conceal)]
|
||||
[InlineData("slowblink", Decoration.SlowBlink)]
|
||||
[InlineData("rapidblink", Decoration.RapidBlink)]
|
||||
[InlineData("strikethrough", Decoration.Strikethrough)]
|
||||
public void Should_Parse_Decoration(string text, Decoration decoration)
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse(text, out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
style.ShouldNotBeNull();
|
||||
style.Decoration.ShouldBe(decoration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Text_And_Decoration()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("bold underline blue on green", out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
style.ShouldNotBeNull();
|
||||
style.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
|
||||
style.Foreground.ShouldBe(Color.Blue);
|
||||
style.Background.ShouldBe(Color.Green);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Background_If_Foreground_Is_Set_To_Default()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("default on green", out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
style.ShouldNotBeNull();
|
||||
style.Decoration.ShouldBe(Decoration.None);
|
||||
style.Foreground.ShouldBe(Color.Default);
|
||||
style.Background.ShouldBe(Color.Green);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Foreground_Is_Set_Twice()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("green yellow", out _);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Background_Is_Set_Twice()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("green on blue yellow", out _);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("bold lol", out _);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("blue on lol", out _);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Spectre.Console/AnsiConsole.Rendering.cs
Normal file
19
src/Spectre.Console/AnsiConsole.Rendering.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Spectre.Console.Composition;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// A console capable of writing ANSI escape sequences.
|
||||
/// </summary>
|
||||
public static partial class AnsiConsole
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the specified object to the console.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The object to render.</param>
|
||||
public static void Render(IRenderable renderable)
|
||||
{
|
||||
Console.Render(renderable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ namespace Spectre.Console
|
||||
{
|
||||
private static readonly Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(() =>
|
||||
{
|
||||
return Create(new AnsiConsoleSettings
|
||||
var console = Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = AnsiSupport.Detect,
|
||||
ColorSystem = ColorSystemSupport.Detect,
|
||||
Out = System.Console.Out,
|
||||
});
|
||||
Created = true;
|
||||
return console;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +28,9 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets the console's capabilities.
|
||||
/// </summary>
|
||||
public static AnsiConsoleCapabilities Capabilities => Console.Capabilities;
|
||||
public static Capabilities Capabilities => Console.Capabilities;
|
||||
|
||||
internal static bool Created { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer width of the console.
|
||||
@@ -63,12 +67,12 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style.
|
||||
/// Gets or sets the text decoration.
|
||||
/// </summary>
|
||||
public static Styles Style
|
||||
public static Decoration Decoration
|
||||
{
|
||||
get => Console.Style;
|
||||
set => Console.Style = value;
|
||||
get => Console.Decoration;
|
||||
set => Console.Decoration = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,7 +87,7 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets colors and styles to the default ones.
|
||||
/// Resets colors and text decorations.
|
||||
/// </summary>
|
||||
public static void Reset()
|
||||
{
|
||||
@@ -91,15 +95,15 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current style back to the default one.
|
||||
/// Resets the current applied text decorations.
|
||||
/// </summary>
|
||||
public static void ResetStyle()
|
||||
public static void ResetDecoration()
|
||||
{
|
||||
Console.ResetStyle();
|
||||
Console.ResetDecoration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the foreground and background colors to the default ones.
|
||||
/// Resets the current applied foreground and background colors.
|
||||
/// </summary>
|
||||
public static void ResetColors()
|
||||
{
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents console capabilities.
|
||||
/// </summary>
|
||||
public sealed class AnsiConsoleCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
/// the console supports Ansi.
|
||||
/// </summary>
|
||||
public bool SupportsAnsi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color system.
|
||||
/// </summary>
|
||||
public ColorSystem ColorSystem { get; }
|
||||
|
||||
internal AnsiConsoleCapabilities(bool supportsAnsi, ColorSystem colorSystem)
|
||||
{
|
||||
SupportsAnsi = supportsAnsi;
|
||||
ColorSystem = colorSystem;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
|
||||
var bits = ColorSystem switch
|
||||
{
|
||||
ColorSystem.NoColors => "1 bit",
|
||||
ColorSystem.Legacy => "3 bits",
|
||||
ColorSystem.Standard => "4 bits",
|
||||
ColorSystem.EightBit => "8 bits",
|
||||
ColorSystem.TrueColor => "24 bits",
|
||||
_ => "?"
|
||||
};
|
||||
|
||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem} ({bits})";
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Spectre.Console/Capabilities.cs
Normal file
59
src/Spectre.Console/Capabilities.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents console capabilities.
|
||||
/// </summary>
|
||||
public sealed class Capabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
/// the console supports Ansi.
|
||||
/// </summary>
|
||||
public bool SupportsAnsi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color system.
|
||||
/// </summary>
|
||||
public ColorSystem ColorSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
/// this is a legacy console (cmd.exe).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only relevant when running on Microsoft Windows.
|
||||
/// </remarks>
|
||||
public bool LegacyConsole { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Capabilities"/> class.
|
||||
/// </summary>
|
||||
/// <param name="supportsAnsi">Whether or not ANSI escape sequences are supported.</param>
|
||||
/// <param name="colorSystem">The color system that is supported.</param>
|
||||
/// <param name="legacyConsole">Whether or not this is a legacy console.</param>
|
||||
public Capabilities(bool supportsAnsi, ColorSystem colorSystem, bool legacyConsole)
|
||||
{
|
||||
SupportsAnsi = supportsAnsi;
|
||||
ColorSystem = colorSystem;
|
||||
LegacyConsole = legacyConsole;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
|
||||
var legacyConsole = LegacyConsole ? "Legacy" : "Modern";
|
||||
var bits = ColorSystem switch
|
||||
{
|
||||
ColorSystem.NoColors => "1 bit",
|
||||
ColorSystem.Legacy => "3 bits",
|
||||
ColorSystem.Standard => "4 bits",
|
||||
ColorSystem.EightBit => "8 bits",
|
||||
ColorSystem.TrueColor => "24 bits",
|
||||
_ => "?"
|
||||
};
|
||||
|
||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
@@ -17,7 +17,7 @@ namespace Spectre.Console
|
||||
|
||||
static Color()
|
||||
{
|
||||
Default = new Color(0, "default", 0, 0, 0, true);
|
||||
Default = new Color(0, 0, 0, 0, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,11 +35,6 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public byte B { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the color, if any.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the color, if any.
|
||||
/// </summary>
|
||||
@@ -62,7 +57,6 @@ namespace Spectre.Console
|
||||
G = green;
|
||||
B = blue;
|
||||
IsDefault = false;
|
||||
Name = null;
|
||||
Number = null;
|
||||
}
|
||||
|
||||
@@ -88,7 +82,8 @@ namespace Spectre.Console
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Color other)
|
||||
{
|
||||
return Number == other.Number || (R == other.R && G == other.G && B == other.B);
|
||||
return (IsDefault && other.IsDefault) ||
|
||||
(IsDefault == other.IsDefault && R == other.R && G == other.G && B == other.B);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,6 +108,15 @@ namespace Spectre.Console
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a <see cref="int"/> to a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="number">The color number to convert.</param>
|
||||
public static implicit operator Color(int number)
|
||||
{
|
||||
return FromInt32(number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
@@ -123,18 +127,12 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a color number into a <see cref="Color"/>.
|
||||
/// Convers a <see cref="Color"/> to a <see cref="ConsoleColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="number">The color number.</param>
|
||||
/// <returns>The color representing the specified color number.</returns>
|
||||
public static Color FromColorNumber(int number)
|
||||
/// <param name="color">The console color to convert.</param>
|
||||
public static implicit operator ConsoleColor(Color color)
|
||||
{
|
||||
if (number < 0 || number > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Color number must be between 0 and 255");
|
||||
}
|
||||
|
||||
return ColorPalette.EightBit.First(x => x.Number == number);
|
||||
return ToConsoleColor(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -179,6 +177,16 @@ namespace Spectre.Console
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a color number into a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="number">The color number.</param>
|
||||
/// <returns>The color representing the specified color number.</returns>
|
||||
public static Color FromInt32(int number)
|
||||
{
|
||||
return ColorTable.GetColor(number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
@@ -207,5 +215,20 @@ namespace Spectre.Console
|
||||
_ => Default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (Number != null)
|
||||
{
|
||||
var name = ColorTable.GetName(Number.Value);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2} (RGB={0},{1},{2})", R, G, B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
src/Spectre.Console/Composition/Border.cs
Normal file
103
src/Spectre.Console/Composition/Border.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a border used by tables.
|
||||
/// </summary>
|
||||
public abstract class Border
|
||||
{
|
||||
private readonly Dictionary<BorderPart, string> _lookup;
|
||||
|
||||
private static readonly Dictionary<BorderKind, Border> _borders = new Dictionary<BorderKind, Border>
|
||||
{
|
||||
{ BorderKind.None, new NoBorder() },
|
||||
{ BorderKind.Ascii, new AsciiBorder() },
|
||||
{ BorderKind.Square, new SquareBorder() },
|
||||
{ BorderKind.Rounded, new RoundedBorder() },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<BorderKind, BorderKind> _safeLookup = new Dictionary<BorderKind, BorderKind>
|
||||
{
|
||||
{ BorderKind.Rounded, BorderKind.Square },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Border"/> class.
|
||||
/// </summary>
|
||||
protected Border()
|
||||
{
|
||||
_lookup = Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>.
|
||||
/// </summary>
|
||||
/// <param name="kind">The kind of border to get.</param>
|
||||
/// <param name="safe">Whether or not to get a "safe" border that can be rendered in a legacy console.</param>
|
||||
/// <returns>A <see cref="Border"/> instance representing the specified <see cref="BorderKind"/>.</returns>
|
||||
public static Border GetBorder(BorderKind kind, bool safe)
|
||||
{
|
||||
if (safe && _safeLookup.TryGetValue(kind, out var safeKind))
|
||||
{
|
||||
kind = safeKind;
|
||||
}
|
||||
|
||||
if (!_borders.TryGetValue(kind, out var border))
|
||||
{
|
||||
throw new InvalidOperationException("Unknown border kind");
|
||||
}
|
||||
|
||||
return border;
|
||||
}
|
||||
|
||||
private Dictionary<BorderPart, string> Initialize()
|
||||
{
|
||||
var lookup = new Dictionary<BorderPart, string>();
|
||||
foreach (BorderPart part in Enum.GetValues(typeof(BorderPart)))
|
||||
{
|
||||
var text = GetBoxPart(part);
|
||||
if (text.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
||||
}
|
||||
|
||||
lookup.Add(part, GetBoxPart(part));
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <param name="count">The number of repetitions.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(BorderPart part, int count)
|
||||
{
|
||||
// TODO: This need some optimization...
|
||||
return string.Join(string.Empty, Enumerable.Repeat(GetBoxPart(part)[0], count));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(BorderPart part)
|
||||
{
|
||||
return _lookup[part].ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character representing the specified border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get the character representation for.</param>
|
||||
/// <returns>A character representation of the specified border part.</returns>
|
||||
protected abstract string GetBoxPart(BorderPart part);
|
||||
}
|
||||
}
|
||||
28
src/Spectre.Console/Composition/BorderKind.cs
Normal file
28
src/Spectre.Console/Composition/BorderKind.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents different kinds of borders.
|
||||
/// </summary>
|
||||
public enum BorderKind
|
||||
{
|
||||
/// <summary>
|
||||
/// No border.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A square border.
|
||||
/// </summary>
|
||||
Square = 1,
|
||||
|
||||
/// <summary>
|
||||
/// An old school ASCII border.
|
||||
/// </summary>
|
||||
Ascii = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A rounded border.
|
||||
/// </summary>
|
||||
Rounded = 3,
|
||||
}
|
||||
}
|
||||
98
src/Spectre.Console/Composition/BorderPart.cs
Normal file
98
src/Spectre.Console/Composition/BorderPart.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the different border parts.
|
||||
/// </summary>
|
||||
public enum BorderPart
|
||||
{
|
||||
/// <summary>
|
||||
/// The top left part of a header.
|
||||
/// </summary>
|
||||
HeaderTopLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The top part of a header.
|
||||
/// </summary>
|
||||
HeaderTop,
|
||||
|
||||
/// <summary>
|
||||
/// The top separator part of a header.
|
||||
/// </summary>
|
||||
HeaderTopSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The top right part of a header.
|
||||
/// </summary>
|
||||
HeaderTopRight,
|
||||
|
||||
/// <summary>
|
||||
/// The left part of a header.
|
||||
/// </summary>
|
||||
HeaderLeft,
|
||||
|
||||
/// <summary>
|
||||
/// A header separator.
|
||||
/// </summary>
|
||||
HeaderSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The right part of a header.
|
||||
/// </summary>
|
||||
HeaderRight,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom left part of a header.
|
||||
/// </summary>
|
||||
HeaderBottomLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom part of a header.
|
||||
/// </summary>
|
||||
HeaderBottom,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom separator part of a header.
|
||||
/// </summary>
|
||||
HeaderBottomSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom right part of a header.
|
||||
/// </summary>
|
||||
HeaderBottomRight,
|
||||
|
||||
/// <summary>
|
||||
/// The left part of a cell.
|
||||
/// </summary>
|
||||
CellLeft,
|
||||
|
||||
/// <summary>
|
||||
/// A cell separator.
|
||||
/// </summary>
|
||||
CellSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The right part of a cell.
|
||||
/// </summary>
|
||||
CellRight,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom left part of a footer.
|
||||
/// </summary>
|
||||
FooterBottomLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom part of a footer.
|
||||
/// </summary>
|
||||
FooterBottom,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom separator part of a footer.
|
||||
/// </summary>
|
||||
FooterBottomSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom right part of a footer.
|
||||
/// </summary>
|
||||
FooterBottomRight,
|
||||
}
|
||||
}
|
||||
37
src/Spectre.Console/Composition/Borders/AsciiBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/AsciiBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an old school ASCII border.
|
||||
/// </summary>
|
||||
public sealed class AsciiBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
BorderPart.HeaderTopLeft => "+",
|
||||
BorderPart.HeaderTop => "-",
|
||||
BorderPart.HeaderTopSeparator => "-",
|
||||
BorderPart.HeaderTopRight => "+",
|
||||
BorderPart.HeaderLeft => "|",
|
||||
BorderPart.HeaderSeparator => "|",
|
||||
BorderPart.HeaderRight => "|",
|
||||
BorderPart.HeaderBottomLeft => "|",
|
||||
BorderPart.HeaderBottom => "-",
|
||||
BorderPart.HeaderBottomSeparator => "+",
|
||||
BorderPart.HeaderBottomRight => "|",
|
||||
BorderPart.CellLeft => "|",
|
||||
BorderPart.CellSeparator => "|",
|
||||
BorderPart.CellRight => "|",
|
||||
BorderPart.FooterBottomLeft => "+",
|
||||
BorderPart.FooterBottom => "-",
|
||||
BorderPart.FooterBottomSeparator => "-",
|
||||
BorderPart.FooterBottomRight => "+",
|
||||
_ => throw new InvalidOperationException("Unknown box part."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Spectre.Console/Composition/Borders/NoBorder.cs
Normal file
14
src/Spectre.Console/Composition/Borders/NoBorder.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an invisible border.
|
||||
/// </summary>
|
||||
public sealed class NoBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Spectre.Console/Composition/Borders/RoundedBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/RoundedBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a rounded border.
|
||||
/// </summary>
|
||||
public sealed class RoundedBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
BorderPart.HeaderTopLeft => "╭",
|
||||
BorderPart.HeaderTop => "─",
|
||||
BorderPart.HeaderTopSeparator => "┬",
|
||||
BorderPart.HeaderTopRight => "╮",
|
||||
BorderPart.HeaderLeft => "│",
|
||||
BorderPart.HeaderSeparator => "│",
|
||||
BorderPart.HeaderRight => "│",
|
||||
BorderPart.HeaderBottomLeft => "├",
|
||||
BorderPart.HeaderBottom => "─",
|
||||
BorderPart.HeaderBottomSeparator => "┼",
|
||||
BorderPart.HeaderBottomRight => "┤",
|
||||
BorderPart.CellLeft => "│",
|
||||
BorderPart.CellSeparator => "│",
|
||||
BorderPart.CellRight => "│",
|
||||
BorderPart.FooterBottomLeft => "╰",
|
||||
BorderPart.FooterBottom => "─",
|
||||
BorderPart.FooterBottomSeparator => "┴",
|
||||
BorderPart.FooterBottomRight => "╯",
|
||||
_ => throw new InvalidOperationException("Unknown box part."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Spectre.Console/Composition/Borders/SquareBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/SquareBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a square border.
|
||||
/// </summary>
|
||||
public sealed class SquareBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
BorderPart.HeaderTopLeft => "┌",
|
||||
BorderPart.HeaderTop => "─",
|
||||
BorderPart.HeaderTopSeparator => "┬",
|
||||
BorderPart.HeaderTopRight => "┐",
|
||||
BorderPart.HeaderLeft => "│",
|
||||
BorderPart.HeaderSeparator => "│",
|
||||
BorderPart.HeaderRight => "│",
|
||||
BorderPart.HeaderBottomLeft => "├",
|
||||
BorderPart.HeaderBottom => "─",
|
||||
BorderPart.HeaderBottomSeparator => "┼",
|
||||
BorderPart.HeaderBottomRight => "┤",
|
||||
BorderPart.CellLeft => "│",
|
||||
BorderPart.CellSeparator => "│",
|
||||
BorderPart.CellRight => "│",
|
||||
BorderPart.FooterBottomLeft => "└",
|
||||
BorderPart.FooterBottom => "─",
|
||||
BorderPart.FooterBottomSeparator => "┴",
|
||||
BorderPart.FooterBottomRight => "┘",
|
||||
_ => throw new InvalidOperationException("Unknown box part."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/Spectre.Console/Composition/Grid.cs
Normal file
120
src/Spectre.Console/Composition/Grid.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Composition;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid.
|
||||
/// </summary>
|
||||
public sealed class Grid : IRenderable
|
||||
{
|
||||
private readonly Table _table;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Grid"/> class.
|
||||
/// </summary>
|
||||
public Grid()
|
||||
{
|
||||
_table = new Table
|
||||
{
|
||||
Border = BorderKind.None,
|
||||
ShowHeaders = false,
|
||||
IsGrid = true,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)_table).Measure(context, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(RenderContext context, int width)
|
||||
{
|
||||
return ((IRenderable)_table).Render(context, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
public void AddColumn()
|
||||
{
|
||||
AddColumn(new GridColumn());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(GridColumn column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_table.AddColumn(new TableColumn(string.Empty)
|
||||
{
|
||||
Width = column.Width,
|
||||
NoWrap = column.NoWrap,
|
||||
Padding = column.Padding,
|
||||
Alignment = column.Alignment,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of columns to add.</param>
|
||||
public void AddColumns(int count)
|
||||
{
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
AddColumn(new GridColumn());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddColumns(params GridColumn[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
foreach (var column in columns)
|
||||
{
|
||||
AddColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new row to the grid.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddRow(params string[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
if (columns.Length < _table.ColumnCount)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are less than the number of grid columns.");
|
||||
}
|
||||
|
||||
if (columns.Length > _table.ColumnCount)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are greater than the number of grid columns.");
|
||||
}
|
||||
|
||||
_table.AddRow(columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Spectre.Console/Composition/GridColumn.cs
Normal file
30
src/Spectre.Console/Composition/GridColumn.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid column.
|
||||
/// </summary>
|
||||
public sealed class GridColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding of the column.
|
||||
/// </summary>
|
||||
public Padding Padding { get; set; } = new Padding(0, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the column.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; } = null;
|
||||
}
|
||||
}
|
||||
26
src/Spectre.Console/Composition/IRenderable.cs
Normal file
26
src/Spectre.Console/Composition/IRenderable.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents something that can be rendered to the console.
|
||||
/// </summary>
|
||||
public interface IRenderable
|
||||
{
|
||||
/// <summary>
|
||||
/// Measures the renderable object.
|
||||
/// </summary>
|
||||
/// <param name="context">The render context.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>The minimum and maximum width of the object.</returns>
|
||||
Measurement Measure(RenderContext context, int maxWidth);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the object.
|
||||
/// </summary>
|
||||
/// <param name="context">The render context.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>A collection of segments.</returns>
|
||||
IEnumerable<Segment> Render(RenderContext context, int maxWidth);
|
||||
}
|
||||
}
|
||||
23
src/Spectre.Console/Composition/Justify.cs
Normal file
23
src/Spectre.Console/Composition/Justify.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents text justification.
|
||||
/// </summary>
|
||||
public enum Justify
|
||||
{
|
||||
/// <summary>
|
||||
/// Left aligned.
|
||||
/// </summary>
|
||||
Left = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Right aligned.
|
||||
/// </summary>
|
||||
Right = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Centered
|
||||
/// </summary>
|
||||
Center = 2,
|
||||
}
|
||||
}
|
||||
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a measurement.
|
||||
/// </summary>
|
||||
public struct Measurement : IEquatable<Measurement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the minimum width.
|
||||
/// </summary>
|
||||
public int Min { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum width.
|
||||
/// </summary>
|
||||
public int Max { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Measurement"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum width.</param>
|
||||
/// <param name="max">The maximum width.</param>
|
||||
public Measurement(int min, int max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Measurement measurement && Equals(measurement);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Min.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Max.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Measurement other)
|
||||
{
|
||||
return Min == other.Min && Max == other.Max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator ==(Measurement left, Measurement right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are not equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator !=(Measurement left, Measurement right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/Spectre.Console/Composition/Padding.cs
Normal file
86
src/Spectre.Console/Composition/Padding.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a measurement.
|
||||
/// </summary>
|
||||
public struct Padding : IEquatable<Padding>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the left padding.
|
||||
/// </summary>
|
||||
public int Left { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right padding.
|
||||
/// </summary>
|
||||
public int Right { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Padding"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="left">The left padding.</param>
|
||||
/// <param name="right">The right padding.</param>
|
||||
public Padding(int left, int right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Padding padding && Equals(padding);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Left.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Right.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Padding other)
|
||||
{
|
||||
return Left == other.Left && Right == other.Right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Padding"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="Padding"/> instance to compare.</param>
|
||||
/// <param name="right">The second <see cref="Padding"/> instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two instances are equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator ==(Padding left, Padding right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Padding"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="Padding"/> instance to compare.</param>
|
||||
/// <param name="right">The second <see cref="Padding"/> instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two instances are not equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator !=(Padding left, Padding right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal padding.
|
||||
/// </summary>
|
||||
/// <returns>The horizontal padding.</returns>
|
||||
public int GetHorizontalPadding()
|
||||
{
|
||||
return Left + Right;
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/Spectre.Console/Composition/Panel.cs
Normal file
132
src/Spectre.Console/Composition/Panel.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a panel which contains another renderable item.
|
||||
/// </summary>
|
||||
public sealed class Panel : IRenderable
|
||||
{
|
||||
private readonly IRenderable _child;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to use
|
||||
/// a "safe" border on legacy consoles that might not be able
|
||||
/// to render non-ASCII characters. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
public bool SafeBorder { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of border to use.
|
||||
/// </summary>
|
||||
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the panel contents.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the panel should
|
||||
/// fit the available space. If <c>false</c>, the panel width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding.
|
||||
/// </summary>
|
||||
public Padding Padding { get; set; } = new Padding(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Panel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The panel content.</param>
|
||||
public Panel(IRenderable content)
|
||||
{
|
||||
_child = content ?? throw new System.ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var childWidth = _child.Measure(context, maxWidth);
|
||||
return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||
{
|
||||
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||
|
||||
var edgeWidth = 2;
|
||||
var paddingWidth = Padding.GetHorizontalPadding();
|
||||
var childWidth = width - edgeWidth - paddingWidth;
|
||||
|
||||
if (!Expand)
|
||||
{
|
||||
var measurement = _child.Measure(context, width - edgeWidth - paddingWidth);
|
||||
childWidth = measurement.Max;
|
||||
}
|
||||
|
||||
var panelWidth = childWidth + paddingWidth;
|
||||
|
||||
// Panel top
|
||||
var result = new List<Segment>
|
||||
{
|
||||
new Segment(border.GetPart(BorderPart.HeaderTopLeft)),
|
||||
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)),
|
||||
new Segment(border.GetPart(BorderPart.HeaderTopRight)),
|
||||
new Segment("\n"),
|
||||
};
|
||||
|
||||
// Render the child.
|
||||
var childContext = context.WithJustification(Alignment);
|
||||
var childSegments = _child.Render(childContext, childWidth);
|
||||
|
||||
// Split the child segments into lines.
|
||||
foreach (var line in Segment.SplitLines(childSegments, panelWidth))
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||
|
||||
// Left padding
|
||||
if (Padding.Left > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', Padding.Left)));
|
||||
}
|
||||
|
||||
var content = new List<Segment>();
|
||||
content.AddRange(line);
|
||||
|
||||
// Do we need to pad the panel?
|
||||
var length = line.Sum(segment => segment.CellLength(context.Encoding));
|
||||
if (length < childWidth)
|
||||
{
|
||||
var diff = childWidth - length;
|
||||
content.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
|
||||
result.AddRange(content);
|
||||
|
||||
// Right padding
|
||||
if (Padding.Right > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', Padding.Right)));
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||
result.Add(new Segment("\n"));
|
||||
}
|
||||
|
||||
// Panel bottom
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(new Segment("\n"));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/Spectre.Console/Composition/RenderContext.cs
Normal file
54
src/Spectre.Console/Composition/RenderContext.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a render context.
|
||||
/// </summary>
|
||||
public sealed class RenderContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the console's output encoding.
|
||||
/// </summary>
|
||||
public Encoding Encoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this a legacy console (i.e. cmd.exe).
|
||||
/// </summary>
|
||||
public bool LegacyConsole { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not unicode is supported.
|
||||
/// </summary>
|
||||
public bool Unicode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current justification.
|
||||
/// </summary>
|
||||
public Justify? Justification { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RenderContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The console's output encoding.</param>
|
||||
/// <param name="legacyConsole">A value indicating whether or not this a legacy console (i.e. cmd.exe).</param>
|
||||
/// <param name="justification">The justification to use when rendering.</param>
|
||||
public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null)
|
||||
{
|
||||
Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding));
|
||||
LegacyConsole = legacyConsole;
|
||||
Justification = justification;
|
||||
Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new context with the specified justification.
|
||||
/// </summary>
|
||||
/// <param name="justification">The justification.</param>
|
||||
/// <returns>A new <see cref="RenderContext"/> instance with the specified justification.</returns>
|
||||
public RenderContext WithJustification(Justify? justification)
|
||||
{
|
||||
return new RenderContext(Encoding, LegacyConsole, justification);
|
||||
}
|
||||
}
|
||||
}
|
||||
231
src/Spectre.Console/Composition/Segment.cs
Normal file
231
src/Spectre.Console/Composition/Segment.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a renderable segment.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{Text,nq}")]
|
||||
public class Segment
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the segment text.
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this is an expicit line break
|
||||
/// that should be preserved.
|
||||
/// </summary>
|
||||
public bool IsLineBreak { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segment style.
|
||||
/// </summary>
|
||||
public Style Style { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Segment"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The segment text.</param>
|
||||
public Segment(string text)
|
||||
: this(text, Style.Plain)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Segment"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The segment text.</param>
|
||||
/// <param name="style">The segment style.</param>
|
||||
public Segment(string text, Style style)
|
||||
: this(text, style, false)
|
||||
{
|
||||
}
|
||||
|
||||
private Segment(string text, Style style, bool lineBreak)
|
||||
{
|
||||
Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
|
||||
Style = style;
|
||||
IsLineBreak = lineBreak;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a segment that represents an implicit line break.
|
||||
/// </summary>
|
||||
/// <returns>A segment that represents an implicit line break.</returns>
|
||||
public static Segment LineBreak()
|
||||
{
|
||||
return new Segment("\n", Style.Plain, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of cells that this segment
|
||||
/// occupies in the console.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding to use.</param>
|
||||
/// <returns>The number of cells that this segment occupies in the console.</returns>
|
||||
public int CellLength(Encoding encoding)
|
||||
{
|
||||
return Text.CellLength(encoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new segment without any trailing line endings.
|
||||
/// </summary>
|
||||
/// <returns>A new segment without any trailing line endings.</returns>
|
||||
public Segment StripLineEndings()
|
||||
{
|
||||
return new Segment(Text.TrimEnd('\n'), Style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the segment at the offset.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset where to split the segment.</param>
|
||||
/// <returns>One or two new segments representing the split.</returns>
|
||||
public (Segment First, Segment Second) Split(int offset)
|
||||
{
|
||||
if (offset < 0)
|
||||
{
|
||||
return (this, null);
|
||||
}
|
||||
|
||||
if (offset >= Text.Length)
|
||||
{
|
||||
return (this, null);
|
||||
}
|
||||
|
||||
return (
|
||||
new Segment(Text.Substring(0, offset), Style),
|
||||
new Segment(Text.Substring(offset, Text.Length - offset), Style));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the provided segments into lines.
|
||||
/// </summary>
|
||||
/// <param name="segments">The segments to split.</param>
|
||||
/// <returns>A collection of lines.</returns>
|
||||
public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments)
|
||||
{
|
||||
return SplitLines(segments, int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the provided segments into lines with a maximum width.
|
||||
/// </summary>
|
||||
/// <param name="segments">The segments to split into lines.</param>
|
||||
/// <param name="maxWidth">The maximum width.</param>
|
||||
/// <returns>A list of lines.</returns>
|
||||
public static List<SegmentLine> SplitLines(IEnumerable<Segment> segments, int maxWidth)
|
||||
{
|
||||
if (segments is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(segments));
|
||||
}
|
||||
|
||||
var lines = new List<SegmentLine>();
|
||||
var line = new SegmentLine();
|
||||
|
||||
var stack = new Stack<Segment>(segments.Reverse());
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var segment = stack.Pop();
|
||||
|
||||
if (line.Length + segment.Text.Length > maxWidth)
|
||||
{
|
||||
var diff = -(maxWidth - (line.Length + segment.Text.Length));
|
||||
var offset = segment.Text.Length - diff;
|
||||
|
||||
var (first, second) = segment.Split(offset);
|
||||
|
||||
line.Add(first);
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
|
||||
if (second != null)
|
||||
{
|
||||
stack.Push(second);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (segment.Text.Contains("\n"))
|
||||
{
|
||||
if (segment.Text == "\n")
|
||||
{
|
||||
if (line.Length > 0 || segment.IsLineBreak)
|
||||
{
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = segment.Text;
|
||||
while (text != null)
|
||||
{
|
||||
var parts = text.SplitLines();
|
||||
if (parts.Length > 0)
|
||||
{
|
||||
if (parts[0].Length > 0)
|
||||
{
|
||||
line.Add(new Segment(parts[0], segment.Style));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
if (line.Length > 0)
|
||||
{
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
}
|
||||
|
||||
text = string.Concat(parts.Skip(1).Take(parts.Length - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
text = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.Add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.Count > 0)
|
||||
{
|
||||
lines.Add(line);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Count < cellHeight)
|
||||
{
|
||||
while (cell.Count != cellHeight)
|
||||
{
|
||||
cell.Add(new SegmentLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/Spectre.Console/Composition/SegmentLine.cs
Normal file
18
src/Spectre.Console/Composition/SegmentLine.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a line of segments.
|
||||
/// </summary>
|
||||
[SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")]
|
||||
public sealed class SegmentLine : List<Segment>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the length of the line.
|
||||
/// </summary>
|
||||
public int Length => this.Sum(line => line.Text.Length);
|
||||
}
|
||||
}
|
||||
118
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
118
src/Spectre.Console/Composition/Table.Calculations.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed partial class Table
|
||||
{
|
||||
// Calculate the widths of each column, including padding, not including borders.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
|
||||
{
|
||||
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth));
|
||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||
|
||||
var tableWidth = widths.Sum();
|
||||
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||
widths = CollapseWidths(widths, wrappable, maxWidth);
|
||||
tableWidth = widths.Sum();
|
||||
|
||||
// last resort, reduce columns evenly
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var excessWidth = tableWidth - maxWidth;
|
||||
widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths);
|
||||
tableWidth = widths.Sum();
|
||||
}
|
||||
}
|
||||
|
||||
if (tableWidth < maxWidth && ShouldExpand())
|
||||
{
|
||||
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
|
||||
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
// Reduce widths so that the total is less or equal to the max width.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
|
||||
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
|
||||
{
|
||||
var totalWidth = widths.Sum();
|
||||
var excessWidth = totalWidth - maxWidth;
|
||||
|
||||
if (wrappable.AnyTrue())
|
||||
{
|
||||
while (totalWidth != 0 && excessWidth > 0)
|
||||
{
|
||||
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second))
|
||||
.Where(x => x.allowWrap)
|
||||
.Max(x => x.width);
|
||||
|
||||
var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 0).Max();
|
||||
var columnDifference = maxColumn - secondMaxColumn;
|
||||
|
||||
var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList();
|
||||
if (!ratios.Any(x => x != 0) || columnDifference == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
|
||||
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
|
||||
|
||||
totalWidth = widths.Sum();
|
||||
excessWidth = totalWidth - maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
|
||||
{
|
||||
var padding = column.Padding.GetHorizontalPadding();
|
||||
|
||||
// Predetermined width?
|
||||
if (column.Width != null)
|
||||
{
|
||||
return (column.Width.Value + padding, column.Width.Value + padding);
|
||||
}
|
||||
|
||||
var columnIndex = _columns.IndexOf(column);
|
||||
var rows = _rows.Select(row => row[columnIndex]);
|
||||
|
||||
var minWidths = new List<int>();
|
||||
var maxWidths = new List<int>();
|
||||
foreach (var row in rows)
|
||||
{
|
||||
var measure = ((IRenderable)row).Measure(options, maxWidth);
|
||||
minWidths.Add(measure.Min);
|
||||
maxWidths.Add(measure.Max);
|
||||
}
|
||||
|
||||
return (minWidths.Count > 0 ? minWidths.Max() : padding,
|
||||
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
||||
}
|
||||
|
||||
private int GetExtraWidth(bool includePadding)
|
||||
{
|
||||
var edges = 2;
|
||||
var separators = _columns.Count - 1;
|
||||
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
|
||||
return separators + edges + padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
358
src/Spectre.Console/Composition/Table.cs
Normal file
358
src/Spectre.Console/Composition/Table.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed partial class Table : IRenderable
|
||||
{
|
||||
private readonly List<TableColumn> _columns;
|
||||
private readonly List<List<Text>> _rows;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of columns in the table.
|
||||
/// </summary>
|
||||
public int ColumnCount => _columns.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of rows in the table.
|
||||
/// </summary>
|
||||
public int RowCount => _rows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of border to use.
|
||||
/// </summary>
|
||||
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not table headers should be shown.
|
||||
/// </summary>
|
||||
public bool ShowHeaders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the table should
|
||||
/// fit the available space. If <c>false</c>, the table width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the table.
|
||||
/// </summary>
|
||||
public int? Width { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to use
|
||||
/// a "safe" border on legacy consoles that might not be able
|
||||
/// to render non-ASCII characters. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
public bool SafeBorder { get; set; } = true;
|
||||
|
||||
internal bool IsGrid { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Table"/> class.
|
||||
/// </summary>
|
||||
public Table()
|
||||
{
|
||||
_columns = new List<TableColumn>();
|
||||
_rows = new List<List<Text>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the table.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(string column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(new TableColumn(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the table.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(TableColumn column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
_columns.Add(column);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple columns to the table.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddColumns(params string[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
_columns.AddRange(columns.Select(column => new TableColumn(column)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple columns to the table.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddColumns(params TableColumn[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
_columns.AddRange(columns.Select(column => column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a row to the table.
|
||||
/// </summary>
|
||||
/// <param name="columns">The row columns to add.</param>
|
||||
public void AddRow(params string[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
if (columns.Length < _columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are less than the number of table columns.");
|
||||
}
|
||||
|
||||
if (columns.Length > _columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
|
||||
}
|
||||
|
||||
_rows.Add(columns.Select(column => Text.New(column)).ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (Width != null)
|
||||
{
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
var measurements = _columns.Select(column => MeasureColumn(column, context, maxWidth)).ToList();
|
||||
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
||||
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||
|
||||
var showBorder = Border != BorderKind.None;
|
||||
var hideBorder = Border == BorderKind.None;
|
||||
|
||||
var maxWidth = width;
|
||||
if (Width != null)
|
||||
{
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
// Calculate the column and table widths
|
||||
var columnWidths = CalculateColumnWidths(context, maxWidth);
|
||||
|
||||
// Update the table width.
|
||||
width = columnWidths.Sum() + GetExtraWidth(includePadding: true);
|
||||
|
||||
var rows = new List<List<Text>>();
|
||||
if (ShowHeaders)
|
||||
{
|
||||
// Add columns to top of rows
|
||||
rows.Add(new List<Text>(_columns.Select(c => c.Text)));
|
||||
}
|
||||
|
||||
// Add rows.
|
||||
rows.AddRange(_rows);
|
||||
|
||||
// Iterate all rows
|
||||
var result = new List<Segment>();
|
||||
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
|
||||
{
|
||||
var cellHeight = 1;
|
||||
|
||||
// Get the list of cells for the row and calculate the cell height
|
||||
var cells = new List<List<SegmentLine>>();
|
||||
foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate())
|
||||
{
|
||||
var justification = _columns[columnIndex].Alignment;
|
||||
var childContext = context.WithJustification(justification);
|
||||
|
||||
var lines = Segment.SplitLines(((IRenderable)cell).Render(childContext, rowWidth));
|
||||
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||
cells.Add(lines);
|
||||
}
|
||||
|
||||
// Show top of header?
|
||||
if (firstRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
||||
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
var padding = _columns[columnIndex].Padding;
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
// Iterate through each cell row
|
||||
foreach (var cellRowIndex in Enumerable.Range(0, cellHeight))
|
||||
{
|
||||
// Make cells the same shape
|
||||
cells = Segment.MakeSameHeight(cellHeight, cells);
|
||||
|
||||
foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate())
|
||||
{
|
||||
if (firstCell && showBorder)
|
||||
{
|
||||
// Show left column edge
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||
}
|
||||
|
||||
// Pad column on left side.
|
||||
if (showBorder || IsGrid)
|
||||
{
|
||||
var leftPadding = _columns[cellIndex].Padding.Left;
|
||||
if (leftPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', leftPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
// Add content
|
||||
result.AddRange(cell[cellRowIndex]);
|
||||
|
||||
// Pad cell content right
|
||||
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding));
|
||||
if (length < columnWidths[cellIndex])
|
||||
{
|
||||
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
|
||||
}
|
||||
|
||||
// Pad column on the right side
|
||||
if (showBorder || (hideBorder && !lastCell) || (IsGrid && !lastCell))
|
||||
{
|
||||
var rightPadding = _columns[cellIndex].Padding.Right;
|
||||
if (rightPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', rightPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCell && showBorder)
|
||||
{
|
||||
// Add right column edge
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||
}
|
||||
else if (showBorder || (hideBorder && !lastCell))
|
||||
{
|
||||
// Add column separator
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
// Show header separator?
|
||||
if (firstRow && showBorder && ShowHeaders)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
var padding = _columns[columnIndex].Padding;
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
|
||||
// Show bottom of footer?
|
||||
if (lastRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
var padding = _columns[columnIndex].Padding;
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool ShouldExpand()
|
||||
{
|
||||
return Expand || Width != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/Spectre.Console/Composition/TableColumn.cs
Normal file
50
src/Spectre.Console/Composition/TableColumn.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table column.
|
||||
/// </summary>
|
||||
public sealed class TableColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text associated with the column.
|
||||
/// </summary>
|
||||
public Text Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding of the column.
|
||||
/// </summary>
|
||||
public Padding Padding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the column.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The table column text.</param>
|
||||
public TableColumn(string text)
|
||||
{
|
||||
Text = Text.New(text ?? throw new ArgumentNullException(nameof(text)));
|
||||
Width = null;
|
||||
Padding = new Padding(1, 1);
|
||||
NoWrap = false;
|
||||
Alignment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
285
src/Spectre.Console/Composition/Text.cs
Normal file
285
src/Spectre.Console/Composition/Text.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents text with color and decorations.
|
||||
/// </summary>
|
||||
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
|
||||
[DebuggerDisplay("{_text,nq}")]
|
||||
public sealed class Text : IRenderable
|
||||
{
|
||||
private readonly List<Span> _spans;
|
||||
private string _text;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text alignment.
|
||||
/// </summary>
|
||||
public Justify Alignment { get; set; } = Justify.Left;
|
||||
|
||||
private sealed class Span
|
||||
{
|
||||
public int Start { get; }
|
||||
public int End { get; }
|
||||
public Style Style { get; }
|
||||
|
||||
public Span(int start, int end, Style style)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Style = style ?? Style.Plain;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Console.Text"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
internal Text(string text)
|
||||
{
|
||||
_text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
|
||||
_spans = new List<Span>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Text"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="foreground">The foreground.</param>
|
||||
/// <param name="background">The background.</param>
|
||||
/// <param name="decoration">The text decoration.</param>
|
||||
/// <returns>A <see cref="Text"/> instance.</returns>
|
||||
public static Text New(
|
||||
string text,
|
||||
Color? foreground = null,
|
||||
Color? background = null,
|
||||
Decoration? decoration = null)
|
||||
{
|
||||
var result = MarkupParser.Parse(text, new Style(foreground, background, decoration));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text alignment.
|
||||
/// </summary>
|
||||
/// <param name="alignment">The text alignment.</param>
|
||||
/// <returns>The same <see cref="Text"/> instance.</returns>
|
||||
public Text WithAlignment(Justify alignment)
|
||||
{
|
||||
Alignment = alignment;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends some text with the specified color and decorations.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to append.</param>
|
||||
/// <param name="style">The text style.</param>
|
||||
public void Append(string text, Style style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
var start = _text.Length;
|
||||
var end = _text.Length + text.Length;
|
||||
|
||||
_text += text;
|
||||
|
||||
Stylize(start, end, style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stylizes a part of the text.
|
||||
/// </summary>
|
||||
/// <param name="start">The start position.</param>
|
||||
/// <param name="end">The end position.</param>
|
||||
/// <param name="style">The style to apply.</param>
|
||||
public void Stylize(int start, int end, Style style)
|
||||
{
|
||||
if (start >= end)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(start), "Start position must be less than the end position.");
|
||||
}
|
||||
|
||||
start = Math.Max(start, 0);
|
||||
end = Math.Min(end, _text.Length);
|
||||
|
||||
_spans.Add(new Span(start, end, style));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_text))
|
||||
{
|
||||
return new Measurement(1, 1);
|
||||
}
|
||||
|
||||
// TODO: Write some kind of tokenizer for this
|
||||
var min = Segment.SplitLines(((IRenderable)this).Render(context, maxWidth))
|
||||
.SelectMany(line => line.Select(segment => segment.Text.Length))
|
||||
.Max();
|
||||
|
||||
var max = _text.SplitLines().Max(x => Cell.GetCellLength(context.Encoding, x));
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_text))
|
||||
{
|
||||
return Array.Empty<Segment>();
|
||||
}
|
||||
|
||||
var result = new List<Segment>();
|
||||
var segments = SplitLineBreaks(CreateSegments());
|
||||
|
||||
var justification = context.Justification ?? Alignment;
|
||||
|
||||
foreach (var (_, _, last, line) in Segment.SplitLines(segments, width).Enumerate())
|
||||
{
|
||||
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));
|
||||
|
||||
if (length < width)
|
||||
{
|
||||
// Justify right side
|
||||
if (justification == Justify.Right)
|
||||
{
|
||||
var diff = width - length;
|
||||
result.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
else if (justification == Justify.Center)
|
||||
{
|
||||
var diff = (width - length) / 2;
|
||||
result.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
}
|
||||
|
||||
// Render the line.
|
||||
foreach (var segment in line)
|
||||
{
|
||||
result.Add(segment.StripLineEndings());
|
||||
}
|
||||
|
||||
// Justify left side
|
||||
if (length < width)
|
||||
{
|
||||
if (justification == Justify.Center)
|
||||
{
|
||||
var diff = (width - length) / 2;
|
||||
result.Add(new Segment(new string(' ', diff)));
|
||||
|
||||
var remainder = (width - length) % 2;
|
||||
if (remainder != 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', remainder)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
result.Add(Segment.LineBreak());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> SplitLineBreaks(IEnumerable<Segment> segments)
|
||||
{
|
||||
// Creates individual segments of line breaks.
|
||||
var result = new List<Segment>();
|
||||
var queue = new Stack<Segment>(segments.Reverse());
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var segment = queue.Pop();
|
||||
|
||||
var index = segment.Text.IndexOf("\n", StringComparison.OrdinalIgnoreCase);
|
||||
if (index == -1)
|
||||
{
|
||||
result.Add(segment);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (first, second) = segment.Split(index);
|
||||
if (!string.IsNullOrEmpty(first.Text))
|
||||
{
|
||||
result.Add(first);
|
||||
}
|
||||
|
||||
result.Add(Segment.LineBreak());
|
||||
queue.Push(new Segment(second.Text.Substring(1), second.Style));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<Segment> CreateSegments()
|
||||
{
|
||||
// This excellent algorithm to sort spans was ported and adapted from
|
||||
// https://github.com/willmcgugan/rich/blob/eb2f0d5277c159d8693636ec60c79c5442fd2e43/rich/text.py#L492
|
||||
|
||||
// Create the style map.
|
||||
var styleMap = _spans.SelectIndex((span, index) => (span, index)).ToDictionary(x => x.index + 1, x => x.span.Style);
|
||||
styleMap[0] = Style.Plain;
|
||||
|
||||
// Create a span list.
|
||||
var spans = new List<(int Offset, bool Leaving, int Style)>();
|
||||
spans.AddRange(_spans.SelectIndex((span, index) => (span.Start, false, index + 1)));
|
||||
spans.AddRange(_spans.SelectIndex((span, index) => (span.End, true, index + 1)));
|
||||
spans = spans.OrderBy(x => x.Offset).ThenBy(x => !x.Leaving).ToList();
|
||||
|
||||
// Keep track of applied styles using a stack
|
||||
var styleStack = new Stack<int>();
|
||||
|
||||
// Now build the segments.
|
||||
var result = new List<Segment>();
|
||||
foreach (var (offset, leaving, style, nextOffset) in BuildSkipList(spans))
|
||||
{
|
||||
if (leaving)
|
||||
{
|
||||
// Leaving
|
||||
styleStack.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Entering
|
||||
styleStack.Push(style);
|
||||
}
|
||||
|
||||
if (nextOffset > offset)
|
||||
{
|
||||
// Build the current style from the stack
|
||||
var styleIndices = styleStack.OrderBy(index => index).ToArray();
|
||||
var currentStyle = Style.Plain.Combine(styleIndices.Select(index => styleMap[index]));
|
||||
|
||||
// Create segment
|
||||
var text = _text.Substring(offset, Math.Min(_text.Length - offset, nextOffset - offset));
|
||||
result.Add(new Segment(text, currentStyle));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<(int Offset, bool Leaving, int Style, int NextOffset)> BuildSkipList(
|
||||
List<(int Offset, bool Leaving, int Style)> spans)
|
||||
{
|
||||
return spans.Zip(spans.Skip(1), (first, second) => (first, second)).Select(
|
||||
x => (x.first.Offset, x.first.Leaving, x.first.Style, NextOffset: x.second.Offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,7 @@ namespace Spectre.Console
|
||||
/// <param name="args">An array of objects to write.</param>
|
||||
public static void Markup(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args)
|
||||
{
|
||||
var result = MarkupParser.Parse(string.Format(provider, format, args));
|
||||
result.Render(console);
|
||||
console.Render(MarkupParser.Parse(string.Format(provider, format, args)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
47
src/Spectre.Console/ConsoleExtensions.Rendering.cs
Normal file
47
src/Spectre.Console/ConsoleExtensions.Rendering.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||
/// </summary>
|
||||
public static partial class ConsoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the specified object to the console.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to render to.</param>
|
||||
/// <param name="renderable">The object to render.</param>
|
||||
public static void Render(this IAnsiConsole console, IRenderable renderable)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
if (renderable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderable));
|
||||
}
|
||||
|
||||
var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole);
|
||||
|
||||
foreach (var segment in renderable.Render(options, console.Width))
|
||||
{
|
||||
if (!segment.Style.Equals(Style.Plain))
|
||||
{
|
||||
using (var style = console.PushStyle(segment.Style))
|
||||
{
|
||||
console.Write(segment.Text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.Write(segment.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console
|
||||
public static partial class ConsoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets both colors and style for the console.
|
||||
/// Resets colors and text decorations.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to reset.</param>
|
||||
public static void Reset(this IAnsiConsole console)
|
||||
@@ -19,25 +19,25 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
console.ResetColors();
|
||||
console.ResetStyle();
|
||||
console.ResetDecoration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current style back to the default one.
|
||||
/// Resets the current applied text decorations.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to reset the style for.</param>
|
||||
public static void ResetStyle(this IAnsiConsole console)
|
||||
/// <param name="console">The console to reset the text decorations for.</param>
|
||||
public static void ResetDecoration(this IAnsiConsole console)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
console.Style = Styles.None;
|
||||
console.Decoration = Decoration.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the foreground and background colors to the default ones.
|
||||
/// Resets the current applied foreground and background colors.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to reset colors for.</param>
|
||||
public static void ResetColors(this IAnsiConsole console)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a style.
|
||||
/// Represents text decoration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Support for different styles is up to the terminal.
|
||||
/// Support for text decorations is up to the terminal.
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
public enum Styles
|
||||
[SuppressMessage("Naming", "CA1714:Flags enums should have plural names")]
|
||||
public enum Decoration
|
||||
{
|
||||
/// <summary>
|
||||
/// No style.
|
||||
/// No text decoration.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
@@ -8,7 +10,7 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets the console's capabilities.
|
||||
/// </summary>
|
||||
AnsiConsoleCapabilities Capabilities { get; }
|
||||
Capabilities Capabilities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer width of the console.
|
||||
@@ -21,9 +23,14 @@ namespace Spectre.Console
|
||||
int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current style.
|
||||
/// Gets the console output encoding.
|
||||
/// </summary>
|
||||
Styles Style { get; set; }
|
||||
Encoding Encoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current text decoration.
|
||||
/// </summary>
|
||||
Decoration Decoration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current foreground.
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace Spectre.Console.Internal
|
||||
public static string GetAnsi(
|
||||
ColorSystem system,
|
||||
string text,
|
||||
Styles style,
|
||||
Decoration decoration,
|
||||
Color foreground,
|
||||
Color background)
|
||||
{
|
||||
var codes = AnsiStyleBuilder.GetAnsiCodes(style);
|
||||
var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration);
|
||||
|
||||
// Got foreground?
|
||||
if (foreground != Color.Default)
|
||||
|
||||
@@ -2,52 +2,52 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class AnsiStyleBuilder
|
||||
internal static class AnsiDecorationBuilder
|
||||
{
|
||||
// TODO: Rewrite this to not yield
|
||||
public static IEnumerable<byte> GetAnsiCodes(Styles style)
|
||||
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
|
||||
{
|
||||
if ((style & Styles.Bold) != 0)
|
||||
if ((decoration & Decoration.Bold) != 0)
|
||||
{
|
||||
yield return 1;
|
||||
}
|
||||
|
||||
if ((style & Styles.Dim) != 0)
|
||||
if ((decoration & Decoration.Dim) != 0)
|
||||
{
|
||||
yield return 2;
|
||||
}
|
||||
|
||||
if ((style & Styles.Italic) != 0)
|
||||
if ((decoration & Decoration.Italic) != 0)
|
||||
{
|
||||
yield return 3;
|
||||
}
|
||||
|
||||
if ((style & Styles.Underline) != 0)
|
||||
if ((decoration & Decoration.Underline) != 0)
|
||||
{
|
||||
yield return 4;
|
||||
}
|
||||
|
||||
if ((style & Styles.SlowBlink) != 0)
|
||||
if ((decoration & Decoration.SlowBlink) != 0)
|
||||
{
|
||||
yield return 5;
|
||||
}
|
||||
|
||||
if ((style & Styles.RapidBlink) != 0)
|
||||
if ((decoration & Decoration.RapidBlink) != 0)
|
||||
{
|
||||
yield return 6;
|
||||
}
|
||||
|
||||
if ((style & Styles.Invert) != 0)
|
||||
if ((decoration & Decoration.Invert) != 0)
|
||||
{
|
||||
yield return 7;
|
||||
}
|
||||
|
||||
if ((style & Styles.Conceal) != 0)
|
||||
if ((decoration & Decoration.Conceal) != 0)
|
||||
{
|
||||
yield return 8;
|
||||
}
|
||||
|
||||
if ((style & Styles.Strikethrough) != 0)
|
||||
if ((decoration & Decoration.Strikethrough) != 0)
|
||||
{
|
||||
yield return 9;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class AnsiDetector
|
||||
{
|
||||
private static readonly Regex[] Regexes = new[]
|
||||
private static readonly Regex[] _regexes = new[]
|
||||
{
|
||||
new Regex("^xterm"), // xterm, PuTTY, Mintty
|
||||
new Regex("^rxvt"), // RXVT
|
||||
@@ -32,12 +32,12 @@ namespace Spectre.Console.Internal
|
||||
new Regex("bvterm"), // Bitvise SSH Client
|
||||
};
|
||||
|
||||
public static bool SupportsAnsi(bool upgrade)
|
||||
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool upgrade)
|
||||
{
|
||||
// Github action doesn't setup a correct PTY but supports ANSI.
|
||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
|
||||
{
|
||||
return true;
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
// Running on Windows?
|
||||
@@ -47,23 +47,24 @@ namespace Spectre.Console.Internal
|
||||
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
|
||||
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
return Windows.SupportsAnsi(upgrade);
|
||||
var supportsAnsi = Windows.SupportsAnsi(upgrade, out var legacyConsole);
|
||||
return (supportsAnsi, legacyConsole);
|
||||
}
|
||||
|
||||
// Check if the terminal is of type ANSI/VT100/xterm compatible.
|
||||
var term = Environment.GetEnvironmentVariable("TERM");
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
if (Regexes.Any(regex => regex.IsMatch(term)))
|
||||
if (_regexes.Any(regex => regex.IsMatch(term)))
|
||||
{
|
||||
return true;
|
||||
return (true, false);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return (false, true);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")]
|
||||
@@ -71,8 +72,10 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
private const int STD_OUTPUT_HANDLE = -11;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
|
||||
|
||||
@@ -89,12 +92,14 @@ namespace Spectre.Console.Internal
|
||||
public static extern uint GetLastError();
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public static bool SupportsAnsi(bool upgrade)
|
||||
public static bool SupportsAnsi(bool upgrade, out bool isLegacy)
|
||||
{
|
||||
isLegacy = false;
|
||||
|
||||
try
|
||||
{
|
||||
var @out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (!GetConsoleMode(@out, out uint mode))
|
||||
if (!GetConsoleMode(@out, out var mode))
|
||||
{
|
||||
// Could not get console mode.
|
||||
return false;
|
||||
@@ -102,6 +107,8 @@ namespace Spectre.Console.Internal
|
||||
|
||||
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
|
||||
{
|
||||
isLegacy = true;
|
||||
|
||||
if (!upgrade)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
@@ -8,8 +9,9 @@ namespace Spectre.Console.Internal
|
||||
private readonly TextWriter _out;
|
||||
private readonly ColorSystem _system;
|
||||
|
||||
public AnsiConsoleCapabilities Capabilities { get; }
|
||||
public Styles Style { get; set; }
|
||||
public Capabilities Capabilities { get; }
|
||||
public Encoding Encoding { get; }
|
||||
public Decoration Decoration { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
public Color Background { get; set; }
|
||||
|
||||
@@ -39,29 +41,16 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system)
|
||||
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
|
||||
{
|
||||
_out = @out ?? throw new ArgumentNullException(nameof(@out));
|
||||
_system = system;
|
||||
|
||||
Capabilities = new AnsiConsoleCapabilities(true, system);
|
||||
Capabilities = new Capabilities(true, system, legacyConsole);
|
||||
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
|
||||
Foreground = Color.Default;
|
||||
Background = Color.Default;
|
||||
Style = Styles.None;
|
||||
}
|
||||
|
||||
public void Reset(bool colors, bool styles)
|
||||
{
|
||||
if (colors)
|
||||
{
|
||||
Foreground = Color.Default;
|
||||
Background = Color.Default;
|
||||
}
|
||||
|
||||
if (styles)
|
||||
{
|
||||
Style = Styles.None;
|
||||
}
|
||||
Decoration = Decoration.None;
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
@@ -71,12 +60,19 @@ namespace Spectre.Console.Internal
|
||||
return;
|
||||
}
|
||||
|
||||
_out.Write(AnsiBuilder.GetAnsi(
|
||||
_system,
|
||||
text,
|
||||
Style,
|
||||
Foreground,
|
||||
Background));
|
||||
var parts = text.NormalizeLineEndings().Split(new[] { '\n' });
|
||||
foreach (var (_, _, last, part) in parts.Enumerate())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
{
|
||||
_out.Write(AnsiBuilder.GetAnsi(_system, part, Decoration, Foreground, Background));
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
_out.Write(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
294
src/Spectre.Console/Internal/Colors/ColorPalette.Generated.cs
Normal file
294
src/Spectre.Console/Internal/Colors/ColorPalette.Generated.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated 2020-08-03 15:17
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
private static List<Color> GenerateLegacyPalette()
|
||||
{
|
||||
return new List<Color>
|
||||
{
|
||||
Color.Black,
|
||||
Color.Maroon,
|
||||
Color.Green,
|
||||
Color.Olive,
|
||||
Color.Navy,
|
||||
Color.Purple,
|
||||
Color.Teal,
|
||||
Color.Silver,
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)
|
||||
{
|
||||
return new List<Color>(legacy)
|
||||
{
|
||||
Color.Grey,
|
||||
Color.Red,
|
||||
Color.Lime,
|
||||
Color.Yellow,
|
||||
Color.Blue,
|
||||
Color.Fuchsia,
|
||||
Color.Aqua,
|
||||
Color.White,
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)
|
||||
{
|
||||
return new List<Color>(standard)
|
||||
{
|
||||
Color.Grey0,
|
||||
Color.NavyBlue,
|
||||
Color.DarkBlue,
|
||||
Color.Blue3,
|
||||
Color.Blue3_1,
|
||||
Color.Blue1,
|
||||
Color.DarkGreen,
|
||||
Color.DeepSkyBlue4,
|
||||
Color.DeepSkyBlue4_1,
|
||||
Color.DeepSkyBlue4_2,
|
||||
Color.DodgerBlue3,
|
||||
Color.DodgerBlue2,
|
||||
Color.Green4,
|
||||
Color.SpringGreen4,
|
||||
Color.Turquoise4,
|
||||
Color.DeepSkyBlue3,
|
||||
Color.DeepSkyBlue3_1,
|
||||
Color.DodgerBlue1,
|
||||
Color.Green3,
|
||||
Color.SpringGreen3,
|
||||
Color.DarkCyan,
|
||||
Color.LightSeaGreen,
|
||||
Color.DeepSkyBlue2,
|
||||
Color.DeepSkyBlue1,
|
||||
Color.Green3_1,
|
||||
Color.SpringGreen3_1,
|
||||
Color.SpringGreen2,
|
||||
Color.Cyan3,
|
||||
Color.DarkTurquoise,
|
||||
Color.Turquoise2,
|
||||
Color.Green1,
|
||||
Color.SpringGreen2_1,
|
||||
Color.SpringGreen1,
|
||||
Color.MediumSpringGreen,
|
||||
Color.Cyan2,
|
||||
Color.Cyan1,
|
||||
Color.DarkRed,
|
||||
Color.DeepPink4,
|
||||
Color.Purple4,
|
||||
Color.Purple4_1,
|
||||
Color.Purple3,
|
||||
Color.BlueViolet,
|
||||
Color.Orange4,
|
||||
Color.Grey37,
|
||||
Color.MediumPurple4,
|
||||
Color.SlateBlue3,
|
||||
Color.SlateBlue3_1,
|
||||
Color.RoyalBlue1,
|
||||
Color.Chartreuse4,
|
||||
Color.DarkSeaGreen4,
|
||||
Color.PaleTurquoise4,
|
||||
Color.SteelBlue,
|
||||
Color.SteelBlue3,
|
||||
Color.CornflowerBlue,
|
||||
Color.Chartreuse3,
|
||||
Color.DarkSeaGreen4_1,
|
||||
Color.CadetBlue,
|
||||
Color.CadetBlue_1,
|
||||
Color.SkyBlue3,
|
||||
Color.SteelBlue1,
|
||||
Color.Chartreuse3_1,
|
||||
Color.PaleGreen3,
|
||||
Color.SeaGreen3,
|
||||
Color.Aquamarine3,
|
||||
Color.MediumTurquoise,
|
||||
Color.SteelBlue1_1,
|
||||
Color.Chartreuse2,
|
||||
Color.SeaGreen2,
|
||||
Color.SeaGreen1,
|
||||
Color.SeaGreen1_1,
|
||||
Color.Aquamarine1,
|
||||
Color.DarkSlateGray2,
|
||||
Color.DarkRed_1,
|
||||
Color.DeepPink4_1,
|
||||
Color.DarkMagenta,
|
||||
Color.DarkMagenta_1,
|
||||
Color.DarkViolet,
|
||||
Color.Purple_1,
|
||||
Color.Orange4_1,
|
||||
Color.LightPink4,
|
||||
Color.Plum4,
|
||||
Color.MediumPurple3,
|
||||
Color.MediumPurple3_1,
|
||||
Color.SlateBlue1,
|
||||
Color.Yellow4,
|
||||
Color.Wheat4,
|
||||
Color.Grey53,
|
||||
Color.LightSlateGrey,
|
||||
Color.MediumPurple,
|
||||
Color.LightSlateBlue,
|
||||
Color.Yellow4_1,
|
||||
Color.DarkOliveGreen3,
|
||||
Color.DarkSeaGreen,
|
||||
Color.LightSkyBlue3,
|
||||
Color.LightSkyBlue3_1,
|
||||
Color.SkyBlue2,
|
||||
Color.Chartreuse2_1,
|
||||
Color.DarkOliveGreen3_1,
|
||||
Color.PaleGreen3_1,
|
||||
Color.DarkSeaGreen3,
|
||||
Color.DarkSlateGray3,
|
||||
Color.SkyBlue1,
|
||||
Color.Chartreuse1,
|
||||
Color.LightGreen,
|
||||
Color.LightGreen_1,
|
||||
Color.PaleGreen1,
|
||||
Color.Aquamarine1_1,
|
||||
Color.DarkSlateGray1,
|
||||
Color.Red3,
|
||||
Color.DeepPink4_2,
|
||||
Color.MediumVioletRed,
|
||||
Color.Magenta3,
|
||||
Color.DarkViolet_1,
|
||||
Color.Purple_2,
|
||||
Color.DarkOrange3,
|
||||
Color.IndianRed,
|
||||
Color.HotPink3,
|
||||
Color.MediumOrchid3,
|
||||
Color.MediumOrchid,
|
||||
Color.MediumPurple2,
|
||||
Color.DarkGoldenrod,
|
||||
Color.LightSalmon3,
|
||||
Color.RosyBrown,
|
||||
Color.Grey63,
|
||||
Color.MediumPurple2_1,
|
||||
Color.MediumPurple1,
|
||||
Color.Gold3,
|
||||
Color.DarkKhaki,
|
||||
Color.NavajoWhite3,
|
||||
Color.Grey69,
|
||||
Color.LightSteelBlue3,
|
||||
Color.LightSteelBlue,
|
||||
Color.Yellow3,
|
||||
Color.DarkOliveGreen3_2,
|
||||
Color.DarkSeaGreen3_1,
|
||||
Color.DarkSeaGreen2,
|
||||
Color.LightCyan3,
|
||||
Color.LightSkyBlue1,
|
||||
Color.GreenYellow,
|
||||
Color.DarkOliveGreen2,
|
||||
Color.PaleGreen1_1,
|
||||
Color.DarkSeaGreen2_1,
|
||||
Color.DarkSeaGreen1,
|
||||
Color.PaleTurquoise1,
|
||||
Color.Red3_1,
|
||||
Color.DeepPink3,
|
||||
Color.DeepPink3_1,
|
||||
Color.Magenta3_1,
|
||||
Color.Magenta3_2,
|
||||
Color.Magenta2,
|
||||
Color.DarkOrange3_1,
|
||||
Color.IndianRed_1,
|
||||
Color.HotPink3_1,
|
||||
Color.HotPink2,
|
||||
Color.Orchid,
|
||||
Color.MediumOrchid1,
|
||||
Color.Orange3,
|
||||
Color.LightSalmon3_1,
|
||||
Color.LightPink3,
|
||||
Color.Pink3,
|
||||
Color.Plum3,
|
||||
Color.Violet,
|
||||
Color.Gold3_1,
|
||||
Color.LightGoldenrod3,
|
||||
Color.Tan,
|
||||
Color.MistyRose3,
|
||||
Color.Thistle3,
|
||||
Color.Plum2,
|
||||
Color.Yellow3_1,
|
||||
Color.Khaki3,
|
||||
Color.LightGoldenrod2,
|
||||
Color.LightYellow3,
|
||||
Color.Grey84,
|
||||
Color.LightSteelBlue1,
|
||||
Color.Yellow2,
|
||||
Color.DarkOliveGreen1,
|
||||
Color.DarkOliveGreen1_1,
|
||||
Color.DarkSeaGreen1_1,
|
||||
Color.Honeydew2,
|
||||
Color.LightCyan1,
|
||||
Color.Red1,
|
||||
Color.DeepPink2,
|
||||
Color.DeepPink1,
|
||||
Color.DeepPink1_1,
|
||||
Color.Magenta2_1,
|
||||
Color.Magenta1,
|
||||
Color.OrangeRed1,
|
||||
Color.IndianRed1,
|
||||
Color.IndianRed1_1,
|
||||
Color.HotPink,
|
||||
Color.HotPink_1,
|
||||
Color.MediumOrchid1_1,
|
||||
Color.DarkOrange,
|
||||
Color.Salmon1,
|
||||
Color.LightCoral,
|
||||
Color.PaleVioletRed1,
|
||||
Color.Orchid2,
|
||||
Color.Orchid1,
|
||||
Color.Orange1,
|
||||
Color.SandyBrown,
|
||||
Color.LightSalmon1,
|
||||
Color.LightPink1,
|
||||
Color.Pink1,
|
||||
Color.Plum1,
|
||||
Color.Gold1,
|
||||
Color.LightGoldenrod2_1,
|
||||
Color.LightGoldenrod2_2,
|
||||
Color.NavajoWhite1,
|
||||
Color.MistyRose1,
|
||||
Color.Thistle1,
|
||||
Color.Yellow1,
|
||||
Color.LightGoldenrod1,
|
||||
Color.Khaki1,
|
||||
Color.Wheat1,
|
||||
Color.Cornsilk1,
|
||||
Color.Grey100,
|
||||
Color.Grey3,
|
||||
Color.Grey7,
|
||||
Color.Grey11,
|
||||
Color.Grey15,
|
||||
Color.Grey19,
|
||||
Color.Grey23,
|
||||
Color.Grey27,
|
||||
Color.Grey30,
|
||||
Color.Grey35,
|
||||
Color.Grey39,
|
||||
Color.Grey42,
|
||||
Color.Grey46,
|
||||
Color.Grey50,
|
||||
Color.Grey54,
|
||||
Color.Grey58,
|
||||
Color.Grey62,
|
||||
Color.Grey66,
|
||||
Color.Grey70,
|
||||
Color.Grey74,
|
||||
Color.Grey78,
|
||||
Color.Grey82,
|
||||
Color.Grey85,
|
||||
Color.Grey89,
|
||||
Color.Grey93,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class ColorPalette
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
public static IReadOnlyList<Color> Legacy { get; }
|
||||
public static IReadOnlyList<Color> Standard { get; }
|
||||
@@ -12,92 +12,15 @@ namespace Spectre.Console.Internal
|
||||
|
||||
static ColorPalette()
|
||||
{
|
||||
Legacy = new List<Color>
|
||||
{
|
||||
Color.Black, Color.Maroon, Color.Green, Color.Olive,
|
||||
Color.Navy, Color.Purple, Color.Teal, Color.Silver,
|
||||
};
|
||||
|
||||
Standard = new List<Color>(Legacy)
|
||||
{
|
||||
Color.Grey, Color.Red, Color.Lime, Color.Yellow,
|
||||
Color.Blue, Color.Fuchsia, Color.Aqua, Color.White,
|
||||
};
|
||||
|
||||
EightBit = new List<Color>(Standard)
|
||||
{
|
||||
Color.Grey0, Color.NavyBlue, Color.DarkBlue, Color.Blue3,
|
||||
Color.Blue3_1, Color.Blue1, Color.DarkGreen, Color.DeepSkyBlue4,
|
||||
Color.DeepSkyBlue4_1, Color.DeepSkyBlue4_2, Color.DodgerBlue3, Color.DodgerBlue2,
|
||||
Color.Green4, Color.SpringGreen4, Color.Turquoise4, Color.DeepSkyBlue3,
|
||||
Color.DeepSkyBlue3_1, Color.DodgerBlue1, Color.Green3, Color.SpringGreen3,
|
||||
Color.DarkCyan, Color.LightSeaGreen, Color.DeepSkyBlue2, Color.DeepSkyBlue1,
|
||||
Color.Green3_1, Color.SpringGreen3_1, Color.SpringGreen2, Color.Cyan3,
|
||||
Color.DarkTurquoise, Color.Turquoise2, Color.Green1, Color.SpringGreen2_1,
|
||||
Color.SpringGreen1, Color.MediumSpringGreen, Color.Cyan2, Color.Cyan1,
|
||||
Color.DarkRed, Color.DeepPink4, Color.Purple4, Color.Purple4_1,
|
||||
Color.Purple3, Color.BlueViolet, Color.Orange4, Color.Grey37,
|
||||
Color.MediumPurple4, Color.SlateBlue3, Color.SlateBlue3_1, Color.RoyalBlue1,
|
||||
Color.Chartreuse4, Color.DarkSeaGreen4, Color.PaleTurquoise4, Color.SteelBlue,
|
||||
Color.SteelBlue3, Color.CornflowerBlue, Color.Chartreuse3, Color.DarkSeaGreen4_1,
|
||||
Color.CadetBlue, Color.CadetBlue_1, Color.SkyBlue3, Color.SteelBlue1,
|
||||
Color.Chartreuse3_1, Color.PaleGreen3, Color.SeaGreen3, Color.Aquamarine3,
|
||||
Color.MediumTurquoise, Color.SteelBlue1_1, Color.Chartreuse2, Color.SeaGreen2,
|
||||
Color.SeaGreen1, Color.SeaGreen1_1, Color.Aquamarine1, Color.DarkSlateGray2,
|
||||
Color.DarkRed_1, Color.DeepPink4_1, Color.DarkMagenta, Color.DarkMagenta_1,
|
||||
Color.DarkViolet, Color.Purple_1, Color.Orange4_1, Color.LightPink4,
|
||||
Color.Plum4, Color.MediumPurple3, Color.MediumPurple3_1, Color.SlateBlue1,
|
||||
Color.Yellow4, Color.Wheat4, Color.Grey53, Color.LightSlateGrey,
|
||||
Color.MediumPurple, Color.LightSlateBlue, Color.Yellow4_1, Color.DarkOliveGreen3,
|
||||
Color.DarkSeaGreen, Color.LightSkyBlue3, Color.LightSkyBlue3_1, Color.SkyBlue2,
|
||||
Color.Chartreuse2_1, Color.DarkOliveGreen3_1, Color.PaleGreen3_1, Color.DarkSeaGreen3,
|
||||
Color.DarkSlateGray3, Color.SkyBlue1, Color.Chartreuse1, Color.LightGreen,
|
||||
Color.LightGreen_1, Color.PaleGreen1, Color.Aquamarine1_1, Color.DarkSlateGray1,
|
||||
Color.Red3, Color.DeepPink4_2, Color.MediumVioletRed, Color.Magenta3,
|
||||
Color.DarkViolet_1, Color.Purple_2, Color.DarkOrange3, Color.IndianRed,
|
||||
Color.HotPink3, Color.MediumOrchid3, Color.MediumOrchid, Color.MediumPurple2,
|
||||
Color.DarkGoldenrod, Color.LightSalmon3, Color.RosyBrown, Color.Grey63,
|
||||
Color.MediumPurple2_1, Color.MediumPurple1, Color.Gold3, Color.DarkKhaki,
|
||||
Color.NavajoWhite3, Color.Grey69, Color.LightSteelBlue3, Color.LightSteelBlue,
|
||||
Color.Yellow3, Color.DarkOliveGreen3_2, Color.DarkSeaGreen3_1, Color.DarkSeaGreen2,
|
||||
Color.LightCyan3, Color.LightSkyBlue1, Color.GreenYellow, Color.DarkOliveGreen2,
|
||||
Color.PaleGreen1_1, Color.DarkSeaGreen2_1, Color.DarkSeaGreen1, Color.PaleTurquoise1,
|
||||
Color.Red3_1, Color.DeepPink3, Color.DeepPink3_1, Color.Magenta3_1,
|
||||
Color.Magenta3_2, Color.Magenta2, Color.DarkOrange3_1, Color.IndianRed_1,
|
||||
Color.HotPink3_1, Color.HotPink2, Color.Orchid, Color.MediumOrchid1,
|
||||
Color.Orange3, Color.LightSalmon3_1, Color.LightPink3, Color.Pink3,
|
||||
Color.Plum3, Color.Violet, Color.Gold3_1, Color.LightGoldenrod3,
|
||||
Color.Tan, Color.MistyRose3, Color.Thistle3, Color.Plum2,
|
||||
Color.Yellow3_1, Color.Khaki3, Color.LightGoldenrod2, Color.LightYellow3,
|
||||
Color.Grey84, Color.LightSteelBlue1, Color.Yellow2, Color.DarkOliveGreen1,
|
||||
Color.DarkOliveGreen1_1, Color.DarkSeaGreen1_1, Color.Honeydew2, Color.LightCyan1,
|
||||
Color.Red1, Color.DeepPink2, Color.DeepPink1, Color.DeepPink1_1,
|
||||
Color.Magenta2_1, Color.Magenta1, Color.OrangeRed1, Color.IndianRed1,
|
||||
Color.IndianRed1_1, Color.HotPink, Color.HotPink_1, Color.MediumOrchid1_1,
|
||||
Color.DarkOrange, Color.Salmon1, Color.LightCoral, Color.PaleVioletRed1,
|
||||
Color.Orchid2, Color.Orchid1, Color.Orange1, Color.SandyBrown,
|
||||
Color.LightSalmon1, Color.LightPink1, Color.Pink1, Color.Plum1,
|
||||
Color.Gold1, Color.LightGoldenrod2_1, Color.LightGoldenrod2_2, Color.NavajoWhite1,
|
||||
Color.MistyRose1, Color.Thistle1, Color.Yellow1, Color.LightGoldenrod1,
|
||||
Color.Khaki1, Color.Wheat1, Color.Cornsilk1, Color.Grey100,
|
||||
Color.Grey3, Color.Grey7, Color.Grey11, Color.Grey15,
|
||||
Color.Grey19, Color.Grey23, Color.Grey27, Color.Grey30,
|
||||
Color.Grey35, Color.Grey39, Color.Grey42, Color.Grey46,
|
||||
Color.Grey50, Color.Grey54, Color.Grey58, Color.Grey62,
|
||||
Color.Grey66, Color.Grey70, Color.Grey74, Color.Grey78,
|
||||
Color.Grey82, Color.Grey85, Color.Grey89, Color.Grey93,
|
||||
};
|
||||
Legacy = GenerateLegacyPalette();
|
||||
Standard = GenerateStandardPalette(Legacy);
|
||||
EightBit = GenerateEightBitPalette(Standard);
|
||||
}
|
||||
|
||||
internal static Color ExactOrClosest(ColorSystem system, Color color)
|
||||
{
|
||||
var exact = Exact(system, color);
|
||||
if (exact != null)
|
||||
{
|
||||
return exact.Value;
|
||||
}
|
||||
|
||||
return Closest(system, color);
|
||||
return exact ?? Closest(system, color);
|
||||
}
|
||||
|
||||
private static Color? Exact(ColorSystem system, Color color)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
if (supportsAnsi)
|
||||
{
|
||||
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)$");
|
||||
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)\\s*$");
|
||||
var match = regex.Match(RuntimeInformation.OSDescription);
|
||||
if (match.Success && int.TryParse(match.Groups["major"].Value, out var major))
|
||||
{
|
||||
|
||||
281
src/Spectre.Console/Internal/Colors/ColorTable.Generated.cs
Normal file
281
src/Spectre.Console/Internal/Colors/ColorTable.Generated.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated 2020-08-03 15:17
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static Dictionary<string, int> GenerateTable()
|
||||
{
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "black", 0 },
|
||||
{ "maroon", 1 },
|
||||
{ "green", 2 },
|
||||
{ "olive", 3 },
|
||||
{ "navy", 4 },
|
||||
{ "purple", 5 },
|
||||
{ "teal", 6 },
|
||||
{ "silver", 7 },
|
||||
{ "grey", 8 },
|
||||
{ "red", 9 },
|
||||
{ "lime", 10 },
|
||||
{ "yellow", 11 },
|
||||
{ "blue", 12 },
|
||||
{ "fuchsia", 13 },
|
||||
{ "aqua", 14 },
|
||||
{ "white", 15 },
|
||||
{ "grey0", 16 },
|
||||
{ "navyblue", 17 },
|
||||
{ "darkblue", 18 },
|
||||
{ "blue3", 19 },
|
||||
{ "blue3_1", 20 },
|
||||
{ "blue1", 21 },
|
||||
{ "darkgreen", 22 },
|
||||
{ "deepskyblue4", 23 },
|
||||
{ "deepskyblue4_1", 24 },
|
||||
{ "deepskyblue4_2", 25 },
|
||||
{ "dodgerblue3", 26 },
|
||||
{ "dodgerblue2", 27 },
|
||||
{ "green4", 28 },
|
||||
{ "springgreen4", 29 },
|
||||
{ "turquoise4", 30 },
|
||||
{ "deepskyblue3", 31 },
|
||||
{ "deepskyblue3_1", 32 },
|
||||
{ "dodgerblue1", 33 },
|
||||
{ "green3", 34 },
|
||||
{ "springgreen3", 35 },
|
||||
{ "darkcyan", 36 },
|
||||
{ "lightseagreen", 37 },
|
||||
{ "deepskyblue2", 38 },
|
||||
{ "deepskyblue1", 39 },
|
||||
{ "green3_1", 40 },
|
||||
{ "springgreen3_1", 41 },
|
||||
{ "springgreen2", 42 },
|
||||
{ "cyan3", 43 },
|
||||
{ "darkturquoise", 44 },
|
||||
{ "turquoise2", 45 },
|
||||
{ "green1", 46 },
|
||||
{ "springgreen2_1", 47 },
|
||||
{ "springgreen1", 48 },
|
||||
{ "mediumspringgreen", 49 },
|
||||
{ "cyan2", 50 },
|
||||
{ "cyan1", 51 },
|
||||
{ "darkred", 52 },
|
||||
{ "deeppink4", 53 },
|
||||
{ "purple4", 54 },
|
||||
{ "purple4_1", 55 },
|
||||
{ "purple3", 56 },
|
||||
{ "blueviolet", 57 },
|
||||
{ "orange4", 58 },
|
||||
{ "grey37", 59 },
|
||||
{ "mediumpurple4", 60 },
|
||||
{ "slateblue3", 61 },
|
||||
{ "slateblue3_1", 62 },
|
||||
{ "royalblue1", 63 },
|
||||
{ "chartreuse4", 64 },
|
||||
{ "darkseagreen4", 65 },
|
||||
{ "paleturquoise4", 66 },
|
||||
{ "steelblue", 67 },
|
||||
{ "steelblue3", 68 },
|
||||
{ "cornflowerblue", 69 },
|
||||
{ "chartreuse3", 70 },
|
||||
{ "darkseagreen4_1", 71 },
|
||||
{ "cadetblue", 72 },
|
||||
{ "cadetblue_1", 73 },
|
||||
{ "skyblue3", 74 },
|
||||
{ "steelblue1", 75 },
|
||||
{ "chartreuse3_1", 76 },
|
||||
{ "palegreen3", 77 },
|
||||
{ "seagreen3", 78 },
|
||||
{ "aquamarine3", 79 },
|
||||
{ "mediumturquoise", 80 },
|
||||
{ "steelblue1_1", 81 },
|
||||
{ "chartreuse2", 82 },
|
||||
{ "seagreen2", 83 },
|
||||
{ "seagreen1", 84 },
|
||||
{ "seagreen1_1", 85 },
|
||||
{ "aquamarine1", 86 },
|
||||
{ "darkslategray2", 87 },
|
||||
{ "darkred_1", 88 },
|
||||
{ "deeppink4_1", 89 },
|
||||
{ "darkmagenta", 90 },
|
||||
{ "darkmagenta_1", 91 },
|
||||
{ "darkviolet", 92 },
|
||||
{ "purple_1", 93 },
|
||||
{ "orange4_1", 94 },
|
||||
{ "lightpink4", 95 },
|
||||
{ "plum4", 96 },
|
||||
{ "mediumpurple3", 97 },
|
||||
{ "mediumpurple3_1", 98 },
|
||||
{ "slateblue1", 99 },
|
||||
{ "yellow4", 100 },
|
||||
{ "wheat4", 101 },
|
||||
{ "grey53", 102 },
|
||||
{ "lightslategrey", 103 },
|
||||
{ "mediumpurple", 104 },
|
||||
{ "lightslateblue", 105 },
|
||||
{ "yellow4_1", 106 },
|
||||
{ "darkolivegreen3", 107 },
|
||||
{ "darkseagreen", 108 },
|
||||
{ "lightskyblue3", 109 },
|
||||
{ "lightskyblue3_1", 110 },
|
||||
{ "skyblue2", 111 },
|
||||
{ "chartreuse2_1", 112 },
|
||||
{ "darkolivegreen3_1", 113 },
|
||||
{ "palegreen3_1", 114 },
|
||||
{ "darkseagreen3", 115 },
|
||||
{ "darkslategray3", 116 },
|
||||
{ "skyblue1", 117 },
|
||||
{ "chartreuse1", 118 },
|
||||
{ "lightgreen", 119 },
|
||||
{ "lightgreen_1", 120 },
|
||||
{ "palegreen1", 121 },
|
||||
{ "aquamarine1_1", 122 },
|
||||
{ "darkslategray1", 123 },
|
||||
{ "red3", 124 },
|
||||
{ "deeppink4_2", 125 },
|
||||
{ "mediumvioletred", 126 },
|
||||
{ "magenta3", 127 },
|
||||
{ "darkviolet_1", 128 },
|
||||
{ "purple_2", 129 },
|
||||
{ "darkorange3", 130 },
|
||||
{ "indianred", 131 },
|
||||
{ "hotpink3", 132 },
|
||||
{ "mediumorchid3", 133 },
|
||||
{ "mediumorchid", 134 },
|
||||
{ "mediumpurple2", 135 },
|
||||
{ "darkgoldenrod", 136 },
|
||||
{ "lightsalmon3", 137 },
|
||||
{ "rosybrown", 138 },
|
||||
{ "grey63", 139 },
|
||||
{ "mediumpurple2_1", 140 },
|
||||
{ "mediumpurple1", 141 },
|
||||
{ "gold3", 142 },
|
||||
{ "darkkhaki", 143 },
|
||||
{ "navajowhite3", 144 },
|
||||
{ "grey69", 145 },
|
||||
{ "lightsteelblue3", 146 },
|
||||
{ "lightsteelblue", 147 },
|
||||
{ "yellow3", 148 },
|
||||
{ "darkolivegreen3_2", 149 },
|
||||
{ "darkseagreen3_1", 150 },
|
||||
{ "darkseagreen2", 151 },
|
||||
{ "lightcyan3", 152 },
|
||||
{ "lightskyblue1", 153 },
|
||||
{ "greenyellow", 154 },
|
||||
{ "darkolivegreen2", 155 },
|
||||
{ "palegreen1_1", 156 },
|
||||
{ "darkseagreen2_1", 157 },
|
||||
{ "darkseagreen1", 158 },
|
||||
{ "paleturquoise1", 159 },
|
||||
{ "red3_1", 160 },
|
||||
{ "deeppink3", 161 },
|
||||
{ "deeppink3_1", 162 },
|
||||
{ "magenta3_1", 163 },
|
||||
{ "magenta3_2", 164 },
|
||||
{ "magenta2", 165 },
|
||||
{ "darkorange3_1", 166 },
|
||||
{ "indianred_1", 167 },
|
||||
{ "hotpink3_1", 168 },
|
||||
{ "hotpink2", 169 },
|
||||
{ "orchid", 170 },
|
||||
{ "mediumorchid1", 171 },
|
||||
{ "orange3", 172 },
|
||||
{ "lightsalmon3_1", 173 },
|
||||
{ "lightpink3", 174 },
|
||||
{ "pink3", 175 },
|
||||
{ "plum3", 176 },
|
||||
{ "violet", 177 },
|
||||
{ "gold3_1", 178 },
|
||||
{ "lightgoldenrod3", 179 },
|
||||
{ "tan", 180 },
|
||||
{ "mistyrose3", 181 },
|
||||
{ "thistle3", 182 },
|
||||
{ "plum2", 183 },
|
||||
{ "yellow3_1", 184 },
|
||||
{ "khaki3", 185 },
|
||||
{ "lightgoldenrod2", 186 },
|
||||
{ "lightyellow3", 187 },
|
||||
{ "grey84", 188 },
|
||||
{ "lightsteelblue1", 189 },
|
||||
{ "yellow2", 190 },
|
||||
{ "darkolivegreen1", 191 },
|
||||
{ "darkolivegreen1_1", 192 },
|
||||
{ "darkseagreen1_1", 193 },
|
||||
{ "honeydew2", 194 },
|
||||
{ "lightcyan1", 195 },
|
||||
{ "red1", 196 },
|
||||
{ "deeppink2", 197 },
|
||||
{ "deeppink1", 198 },
|
||||
{ "deeppink1_1", 199 },
|
||||
{ "magenta2_1", 200 },
|
||||
{ "magenta1", 201 },
|
||||
{ "orangered1", 202 },
|
||||
{ "indianred1", 203 },
|
||||
{ "indianred1_1", 204 },
|
||||
{ "hotpink", 205 },
|
||||
{ "hotpink_1", 206 },
|
||||
{ "mediumorchid1_1", 207 },
|
||||
{ "darkorange", 208 },
|
||||
{ "salmon1", 209 },
|
||||
{ "lightcoral", 210 },
|
||||
{ "palevioletred1", 211 },
|
||||
{ "orchid2", 212 },
|
||||
{ "orchid1", 213 },
|
||||
{ "orange1", 214 },
|
||||
{ "sandybrown", 215 },
|
||||
{ "lightsalmon1", 216 },
|
||||
{ "lightpink1", 217 },
|
||||
{ "pink1", 218 },
|
||||
{ "plum1", 219 },
|
||||
{ "gold1", 220 },
|
||||
{ "lightgoldenrod2_1", 221 },
|
||||
{ "lightgoldenrod2_2", 222 },
|
||||
{ "navajowhite1", 223 },
|
||||
{ "mistyrose1", 224 },
|
||||
{ "thistle1", 225 },
|
||||
{ "yellow1", 226 },
|
||||
{ "lightgoldenrod1", 227 },
|
||||
{ "khaki1", 228 },
|
||||
{ "wheat1", 229 },
|
||||
{ "cornsilk1", 230 },
|
||||
{ "grey100", 231 },
|
||||
{ "grey3", 232 },
|
||||
{ "grey7", 233 },
|
||||
{ "grey11", 234 },
|
||||
{ "grey15", 235 },
|
||||
{ "grey19", 236 },
|
||||
{ "grey23", 237 },
|
||||
{ "grey27", 238 },
|
||||
{ "grey30", 239 },
|
||||
{ "grey35", 240 },
|
||||
{ "grey39", 241 },
|
||||
{ "grey42", 242 },
|
||||
{ "grey46", 243 },
|
||||
{ "grey50", 244 },
|
||||
{ "grey54", 245 },
|
||||
{ "grey58", 246 },
|
||||
{ "grey62", 247 },
|
||||
{ "grey66", 248 },
|
||||
{ "grey70", 249 },
|
||||
{ "grey74", 250 },
|
||||
{ "grey78", 251 },
|
||||
{ "grey82", 252 },
|
||||
{ "grey85", 253 },
|
||||
{ "grey89", 254 },
|
||||
{ "grey93", 255 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/Spectre.Console/Internal/Colors/ColorTable.cs
Normal file
54
src/Spectre.Console/Internal/Colors/ColorTable.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static readonly Dictionary<int, string> _nameLookup;
|
||||
private static readonly Dictionary<string, int> _numberLookup;
|
||||
|
||||
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
|
||||
static ColorTable()
|
||||
{
|
||||
_numberLookup = GenerateTable();
|
||||
_nameLookup = new Dictionary<int, string>();
|
||||
foreach (var pair in _numberLookup)
|
||||
{
|
||||
_nameLookup.Add(pair.Value, pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color GetColor(int number)
|
||||
{
|
||||
if (number < 0 || number > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Color number must be between 0 and 255");
|
||||
}
|
||||
|
||||
return ColorPalette.EightBit[number];
|
||||
}
|
||||
|
||||
public static Color? GetColor(string name)
|
||||
{
|
||||
if (!_numberLookup.TryGetValue(name, out var number))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (number > ColorPalette.EightBit.Count - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ColorPalette.EightBit[number];
|
||||
}
|
||||
|
||||
public static string GetName(int number)
|
||||
{
|
||||
_nameLookup.TryGetValue(number, out var name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
@@ -13,9 +14,41 @@ namespace Spectre.Console.Internal
|
||||
|
||||
var buffer = settings.Out ?? System.Console.Out;
|
||||
|
||||
var supportsAnsi = settings.Ansi == AnsiSupport.Detect
|
||||
? AnsiDetector.SupportsAnsi(true)
|
||||
: settings.Ansi == AnsiSupport.Yes;
|
||||
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
|
||||
var legacyConsole = false;
|
||||
|
||||
if (settings.Ansi == AnsiSupport.Detect)
|
||||
{
|
||||
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(true);
|
||||
|
||||
// Check whether or not this is a legacy console from the existing instance (if any).
|
||||
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
|
||||
if (AnsiConsole.Created && !legacyConsole && buffer.IsStandardOut() && AnsiConsole.Capabilities.LegacyConsole)
|
||||
{
|
||||
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffer.IsStandardOut())
|
||||
{
|
||||
// Are we running on Windows?
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Not the first console we're creating?
|
||||
if (AnsiConsole.Created)
|
||||
{
|
||||
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try detecting whether or not this
|
||||
(_, legacyConsole) = AnsiDetector.Detect(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
|
||||
? ColorSystemDetector.Detect(supportsAnsi)
|
||||
@@ -23,13 +56,13 @@ namespace Spectre.Console.Internal
|
||||
|
||||
if (supportsAnsi)
|
||||
{
|
||||
return new AnsiConsoleRenderer(buffer, colorSystem)
|
||||
return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole)
|
||||
{
|
||||
Style = Styles.None,
|
||||
Decoration = Decoration.None,
|
||||
};
|
||||
}
|
||||
|
||||
return new FallbackConsoleRenderer(buffer, colorSystem);
|
||||
return new FallbackConsoleRenderer(buffer, colorSystem, legacyConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
src/Spectre.Console/Internal/DecorationTable.cs
Normal file
35
src/Spectre.Console/Internal/DecorationTable.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class DecorationTable
|
||||
{
|
||||
private static readonly Dictionary<string, Decoration?> _lookup;
|
||||
|
||||
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
|
||||
static DecorationTable()
|
||||
{
|
||||
_lookup = new Dictionary<string, Decoration?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "none", Decoration.None },
|
||||
{ "bold", Decoration.Bold },
|
||||
{ "dim", Decoration.Dim },
|
||||
{ "italic", Decoration.Italic },
|
||||
{ "underline", Decoration.Underline },
|
||||
{ "invert", Decoration.Invert },
|
||||
{ "conceal", Decoration.Conceal },
|
||||
{ "slowblink", Decoration.SlowBlink },
|
||||
{ "rapidblink", Decoration.RapidBlink },
|
||||
{ "strikethrough", Decoration.Strikethrough },
|
||||
};
|
||||
}
|
||||
|
||||
public static Decoration? GetDecoration(string name)
|
||||
{
|
||||
_lookup.TryGetValue(name, out var result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,25 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class ConsoleExtensions
|
||||
{
|
||||
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
if (style is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(style));
|
||||
}
|
||||
|
||||
var current = new Style(console.Foreground, console.Background, console.Decoration);
|
||||
console.SetColor(style.Foreground, true);
|
||||
console.SetColor(style.Background, false);
|
||||
console.Decoration = style.Decoration;
|
||||
return new StyleScope(console, current);
|
||||
}
|
||||
|
||||
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
|
||||
{
|
||||
if (console is null)
|
||||
@@ -17,16 +36,16 @@ namespace Spectre.Console.Internal
|
||||
return new ColorScope(console, current, foreground);
|
||||
}
|
||||
|
||||
public static IDisposable PushStyle(this IAnsiConsole console, Styles style)
|
||||
public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
var current = console.Style;
|
||||
console.Style = style;
|
||||
return new StyleScope(console, current);
|
||||
var current = console.Decoration;
|
||||
console.Decoration = decoration;
|
||||
return new DecorationScope(console, current);
|
||||
}
|
||||
|
||||
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
|
||||
@@ -47,6 +66,33 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StyleScope : IDisposable
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly Style _style;
|
||||
|
||||
public StyleScope(IAnsiConsole console, Style style)
|
||||
{
|
||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
_style = style ?? throw new ArgumentNullException(nameof(style));
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
|
||||
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
|
||||
~StyleScope()
|
||||
{
|
||||
throw new InvalidOperationException("Style scope was not disposed.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_console.SetColor(_style.Foreground, true);
|
||||
_console.SetColor(_style.Background, false);
|
||||
_console.Decoration = _style.Decoration;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ColorScope : IDisposable
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
@@ -74,28 +120,28 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StyleScope : IDisposable
|
||||
internal sealed class DecorationScope : IDisposable
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly Styles _style;
|
||||
private readonly Decoration _decoration;
|
||||
|
||||
public StyleScope(IAnsiConsole console, Styles color)
|
||||
public DecorationScope(IAnsiConsole console, Decoration decoration)
|
||||
{
|
||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
_style = color;
|
||||
_decoration = decoration;
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
|
||||
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
|
||||
~StyleScope()
|
||||
~DecorationScope()
|
||||
{
|
||||
throw new InvalidOperationException("Style scope was not disposed.");
|
||||
throw new InvalidOperationException("Decoration scope was not disposed.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_console.Style = _style;
|
||||
_console.Decoration = _decoration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class EnumerableExtensions
|
||||
{
|
||||
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AnyTrue(this IEnumerable<bool> source)
|
||||
{
|
||||
return source.Any(b => b);
|
||||
}
|
||||
|
||||
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
return Enumerate(source.GetEnumerator());
|
||||
}
|
||||
|
||||
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerator<T> source)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
var first = true;
|
||||
var last = !source.MoveNext();
|
||||
T current;
|
||||
|
||||
for (var index = 0; !last; index++)
|
||||
{
|
||||
current = source.Current;
|
||||
last = !source.MoveNext();
|
||||
yield return (index, first, last, current);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TResult> SelectIndex<T, TResult>(this IEnumerable<T> source, Func<T, int, TResult> func)
|
||||
{
|
||||
return source.Select((value, index) => func(value, index));
|
||||
}
|
||||
|
||||
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
|
||||
this IEnumerable<TFirst> source, IEnumerable<TSecond> first)
|
||||
{
|
||||
return source.Zip(first, (first, second) => (first, second));
|
||||
}
|
||||
|
||||
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
|
||||
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
|
||||
{
|
||||
return first.Zip(second, (a, b) => (a, b))
|
||||
.Zip(third, (a, b) => (a.a, a.b, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/Spectre.Console/Internal/Extensions/StringExtensions.cs
Normal file
41
src/Spectre.Console/Internal/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
||||
// Cache whether or not internally normalized line endings
|
||||
// already are normalized. No reason to do yet another replace if it is.
|
||||
private static readonly bool _alreadyNormalized
|
||||
= Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static int CellLength(this string text, Encoding encoding)
|
||||
{
|
||||
return Cell.GetCellLength(encoding, text);
|
||||
}
|
||||
|
||||
public static string NormalizeLineEndings(this string text, bool native = false)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalized = text?.Replace("\r\n", "\n")
|
||||
?.Replace("\r", string.Empty);
|
||||
|
||||
if (native && !_alreadyNormalized)
|
||||
{
|
||||
normalized = normalized.Replace("\n", Environment.NewLine);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
public static string[] SplitLines(this string text)
|
||||
{
|
||||
return text.NormalizeLineEndings().Split(new[] { '\n' }, StringSplitOptions.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Spectre.Console/Internal/Extensions/StyleExtensions.cs
Normal file
29
src/Spectre.Console/Internal/Extensions/StyleExtensions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class StyleExtensions
|
||||
{
|
||||
public static Style Combine(this Style style, IEnumerable<Style> source)
|
||||
{
|
||||
if (style is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(style));
|
||||
}
|
||||
|
||||
if (source is null)
|
||||
{
|
||||
return style;
|
||||
}
|
||||
|
||||
var current = style;
|
||||
foreach (var item in source)
|
||||
{
|
||||
current = current.Combine(item);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
@@ -13,7 +14,9 @@ namespace Spectre.Console.Internal
|
||||
private ConsoleColor _foreground;
|
||||
private ConsoleColor _background;
|
||||
|
||||
public AnsiConsoleCapabilities Capabilities { get; }
|
||||
public Capabilities Capabilities { get; }
|
||||
|
||||
public Encoding Encoding { get; }
|
||||
|
||||
public int Width
|
||||
{
|
||||
@@ -41,7 +44,7 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
public Styles Style { get; set; }
|
||||
public Decoration Decoration { get; set; }
|
||||
|
||||
public Color Foreground
|
||||
{
|
||||
@@ -82,28 +85,32 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system)
|
||||
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
|
||||
{
|
||||
_out = @out;
|
||||
_system = system;
|
||||
|
||||
Capabilities = new AnsiConsoleCapabilities(false, _system);
|
||||
|
||||
if (_out.IsStandardOut())
|
||||
{
|
||||
_defaultForeground = System.Console.ForegroundColor;
|
||||
_defaultBackground = System.Console.BackgroundColor;
|
||||
|
||||
Encoding = System.Console.OutputEncoding;
|
||||
}
|
||||
else
|
||||
{
|
||||
_defaultForeground = ConsoleColor.Gray;
|
||||
_defaultBackground = ConsoleColor.Black;
|
||||
|
||||
Encoding = Encoding.UTF8;
|
||||
}
|
||||
|
||||
Capabilities = new Capabilities(false, _system, legacyConsole);
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
_out.Write(text);
|
||||
_out.Write(text.NormalizeLineEndings(native: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class Lookup
|
||||
{
|
||||
private readonly Dictionary<string, Styles?> _styles;
|
||||
private readonly Dictionary<string, Color?> _colors;
|
||||
|
||||
private static readonly Lazy<Lookup> _lazy = new Lazy<Lookup>(() => new Lookup());
|
||||
public static Lookup Instance => _lazy.Value;
|
||||
|
||||
private Lookup()
|
||||
{
|
||||
_styles = new Dictionary<string, Styles?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "bold", Styles.Bold },
|
||||
{ "dim", Styles.Dim },
|
||||
{ "italic", Styles.Italic },
|
||||
{ "underline", Styles.Underline },
|
||||
{ "invert", Styles.Invert },
|
||||
{ "conceal", Styles.Conceal },
|
||||
{ "slowblink", Styles.SlowBlink },
|
||||
{ "rapidblink", Styles.RapidBlink },
|
||||
{ "strikethrough", Styles.Strikethrough },
|
||||
};
|
||||
|
||||
_colors = new Dictionary<string, Color?>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var color in ColorPalette.EightBit)
|
||||
{
|
||||
_colors.Add(color.Name, color);
|
||||
}
|
||||
}
|
||||
|
||||
public Styles? GetStyle(string name)
|
||||
{
|
||||
_styles.TryGetValue(name, out var style);
|
||||
return style;
|
||||
}
|
||||
|
||||
public Color? GetColor(string name)
|
||||
{
|
||||
_colors.TryGetValue(name, out var color);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupBlockNode : IMarkupNode
|
||||
{
|
||||
private readonly List<IMarkupNode> _elements;
|
||||
|
||||
public MarkupBlockNode()
|
||||
{
|
||||
_elements = new List<IMarkupNode>();
|
||||
}
|
||||
|
||||
public void Append(IMarkupNode element)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
_elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
element.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupStyleNode : IMarkupNode
|
||||
{
|
||||
private readonly Styles? _style;
|
||||
private readonly Color? _foreground;
|
||||
private readonly Color? _background;
|
||||
private readonly IMarkupNode _element;
|
||||
|
||||
public MarkupStyleNode(
|
||||
Styles? style,
|
||||
Color? foreground,
|
||||
Color? background,
|
||||
IMarkupNode element)
|
||||
{
|
||||
_style = style;
|
||||
_foreground = foreground;
|
||||
_background = background;
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
var style = (IDisposable)null;
|
||||
var foreground = (IDisposable)null;
|
||||
var background = (IDisposable)null;
|
||||
|
||||
if (_style != null)
|
||||
{
|
||||
style = renderer.PushStyle(_style.Value);
|
||||
}
|
||||
|
||||
if (_foreground != null)
|
||||
{
|
||||
foreground = renderer.PushColor(_foreground.Value, foreground: true);
|
||||
}
|
||||
|
||||
if (_background != null)
|
||||
{
|
||||
background = renderer.PushColor(_background.Value, foreground: false);
|
||||
}
|
||||
|
||||
_element.Render(renderer);
|
||||
|
||||
background?.Dispose();
|
||||
foreground?.Dispose();
|
||||
style?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupTextNode : IMarkupNode
|
||||
{
|
||||
public string Text { get; }
|
||||
|
||||
public MarkupTextNode(string text)
|
||||
{
|
||||
Text = text ?? throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
renderer.Write(Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a parsed markup node.
|
||||
/// </summary>
|
||||
internal interface IMarkupNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the node using the specified renderer.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The renderer to use.</param>
|
||||
void Render(IAnsiConsole renderer);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class MarkupParser
|
||||
{
|
||||
public static IMarkupNode Parse(string text)
|
||||
{
|
||||
using var tokenizer = new MarkupTokenizer(text);
|
||||
var root = new MarkupBlockNode();
|
||||
|
||||
var stack = new Stack<MarkupBlockNode>();
|
||||
var current = root;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var token = tokenizer.GetNext();
|
||||
if (token == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.Kind == MarkupTokenKind.Text)
|
||||
{
|
||||
current.Append(new MarkupTextNode(token.Value));
|
||||
continue;
|
||||
}
|
||||
else if (token.Kind == MarkupTokenKind.Open)
|
||||
{
|
||||
var (style, foreground, background) = MarkupStyleParser.Parse(token.Value);
|
||||
var content = new MarkupBlockNode();
|
||||
current.Append(new MarkupStyleNode(style, foreground, background, content));
|
||||
|
||||
current = content;
|
||||
stack.Push(current);
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (token.Kind == MarkupTokenKind.Close)
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered closing tag when none was expected near position {token.Position}.");
|
||||
}
|
||||
|
||||
stack.Pop();
|
||||
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
current = root;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = stack.Peek();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Encountered unkown markup token.");
|
||||
}
|
||||
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?");
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class MarkupStyleParser
|
||||
{
|
||||
public static (Styles? Style, Color? Foreground, Color? Background) Parse(string text)
|
||||
{
|
||||
var effectiveStyle = (Styles?)null;
|
||||
var effectiveForeground = (Color?)null;
|
||||
var effectiveBackground = (Color?)null;
|
||||
|
||||
var parts = text.Split(new[] { ' ' });
|
||||
var foreground = true;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreground = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var style = Lookup.Instance.GetStyle(part);
|
||||
if (style != null)
|
||||
{
|
||||
if (effectiveStyle == null)
|
||||
{
|
||||
effectiveStyle = Styles.None;
|
||||
}
|
||||
|
||||
effectiveStyle |= style.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var color = Lookup.Instance.GetColor(part);
|
||||
if (color == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find color..");
|
||||
}
|
||||
|
||||
if (foreground)
|
||||
{
|
||||
if (effectiveForeground != null)
|
||||
{
|
||||
throw new InvalidOperationException("A foreground has already been set.");
|
||||
}
|
||||
|
||||
effectiveForeground = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectiveBackground != null)
|
||||
{
|
||||
throw new InvalidOperationException("A background has already been set.");
|
||||
}
|
||||
|
||||
effectiveBackground = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (effectiveStyle, effectiveForeground, effectiveBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/Spectre.Console/Internal/Text/Cell.cs
Normal file
150
src/Spectre.Console/Internal/Text/Cell.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
// Taken and modified from NStack project by Miguel de Icaza, licensed under BSD-3
|
||||
// https://github.com/migueldeicaza/NStack/blob/3fc024fb2c2e99927d3e12991570fb54db8ce01e/NStack/unicode/Rune.ColumnWidth.cs
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class Cell
|
||||
{
|
||||
[SuppressMessage("Design", "RCS1169:Make field read-only.")]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces for multi-line statements should not share line")]
|
||||
private static readonly uint[,] _combining = new uint[,]
|
||||
{
|
||||
{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
|
||||
{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
|
||||
{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
|
||||
{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
|
||||
{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
|
||||
{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
|
||||
{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
|
||||
{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
|
||||
{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
|
||||
{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
|
||||
{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
|
||||
{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
|
||||
{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
|
||||
{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
|
||||
{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
|
||||
{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
|
||||
{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
|
||||
{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
|
||||
{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
|
||||
{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
|
||||
{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
|
||||
{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
|
||||
{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
|
||||
{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
|
||||
{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
|
||||
{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
|
||||
{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
|
||||
{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
|
||||
{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
|
||||
{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
|
||||
{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
|
||||
{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
|
||||
{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
|
||||
{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
|
||||
{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
|
||||
{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
|
||||
{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
|
||||
{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
|
||||
{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
|
||||
{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
|
||||
{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
|
||||
{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
|
||||
{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
|
||||
{ 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
|
||||
{ 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
|
||||
{ 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
|
||||
{ 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
|
||||
{ 0xE0100, 0xE01EF },
|
||||
};
|
||||
|
||||
public static int GetCellLength(Encoding encoding, string text)
|
||||
{
|
||||
return text.Sum(c => GetCellLength(encoding, c));
|
||||
}
|
||||
|
||||
public static int GetCellLength(Encoding encoding, char rune)
|
||||
{
|
||||
// Is it represented by a single byte?
|
||||
// In that case we don't have to calculate the
|
||||
// actual cell width.
|
||||
if (encoding.GetByteCount(new[] { rune }) == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var irune = (uint)rune;
|
||||
if (irune < 32)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (irune < 127)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (irune >= 0x7f && irune <= 0xa0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Binary search in table of non-spacing characters
|
||||
if (BinarySearch(irune, _combining, _combining.GetLength(0) - 1) != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we arrive here, ucs is not a combining or C0/C1 control character
|
||||
return 1 +
|
||||
((irune >= 0x1100 &&
|
||||
(irune <= 0x115f || /* Hangul Jamo init. consonants */
|
||||
irune == 0x2329 || irune == 0x232a ||
|
||||
(irune >= 0x2e80 && irune <= 0xa4cf &&
|
||||
irune != 0x303f) || /* CJK ... Yi */
|
||||
(irune >= 0xac00 && irune <= 0xd7a3) || /* Hangul Syllables */
|
||||
(irune >= 0xf900 && irune <= 0xfaff) || /* CJK Compatibility Ideographs */
|
||||
(irune >= 0xfe10 && irune <= 0xfe19) || /* Vertical forms */
|
||||
(irune >= 0xfe30 && irune <= 0xfe6f) || /* CJK Compatibility Forms */
|
||||
(irune >= 0xff00 && irune <= 0xff60) || /* Fullwidth Forms */
|
||||
(irune >= 0xffe0 && irune <= 0xffe6) ||
|
||||
(irune >= 0x20000 && irune <= 0x2fffd) ||
|
||||
(irune >= 0x30000 && irune <= 0x3fffd))) ? 1 : 0);
|
||||
}
|
||||
|
||||
private static int BinarySearch(uint rune, uint[,] table, int max)
|
||||
{
|
||||
var min = 0;
|
||||
int mid;
|
||||
|
||||
if (rune < table[0, 0] || rune > table[max, 1])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (max >= min)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
if (rune > table[mid, 1])
|
||||
{
|
||||
min = mid + 1;
|
||||
}
|
||||
else if (rune < table[mid, 0])
|
||||
{
|
||||
max = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/Spectre.Console/Internal/Text/Markup/MarkupParser.cs
Normal file
56
src/Spectre.Console/Internal/Text/Markup/MarkupParser.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class MarkupParser
|
||||
{
|
||||
public static Text Parse(string text, Style style = null)
|
||||
{
|
||||
style ??= Style.Plain;
|
||||
|
||||
var result = new Text(string.Empty);
|
||||
using var tokenizer = new MarkupTokenizer(text);
|
||||
|
||||
var stack = new Stack<Style>();
|
||||
|
||||
while (tokenizer.MoveNext())
|
||||
{
|
||||
var token = tokenizer.Current;
|
||||
|
||||
if (token.Kind == MarkupTokenKind.Open)
|
||||
{
|
||||
var parsedStyle = StyleParser.Parse(token.Value);
|
||||
stack.Push(parsedStyle);
|
||||
}
|
||||
else if (token.Kind == MarkupTokenKind.Close)
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered closing tag when none was expected near position {token.Position}.");
|
||||
}
|
||||
|
||||
stack.Pop();
|
||||
}
|
||||
else if (token.Kind == MarkupTokenKind.Text)
|
||||
{
|
||||
// Get the effecive style.
|
||||
var effectiveStyle = style.Combine(stack.Reverse());
|
||||
result.Append(token.Value, effectiveStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Encountered unkown markup token.");
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
private readonly StringBuffer _reader;
|
||||
|
||||
public MarkupToken Current { get; private set; }
|
||||
|
||||
public MarkupTokenizer(string text)
|
||||
{
|
||||
_reader = new StringBuffer(text ?? throw new ArgumentNullException(nameof(text)));
|
||||
@@ -17,11 +19,11 @@ namespace Spectre.Console.Internal
|
||||
_reader.Dispose();
|
||||
}
|
||||
|
||||
public MarkupToken GetNext()
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_reader.Eof)
|
||||
{
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var current = _reader.Peek();
|
||||
@@ -40,7 +42,8 @@ namespace Spectre.Console.Internal
|
||||
if (current == '[')
|
||||
{
|
||||
_reader.Read();
|
||||
return new MarkupToken(MarkupTokenKind.Text, "[", position);
|
||||
Current = new MarkupToken(MarkupTokenKind.Text, "[", position);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (current == '/')
|
||||
@@ -59,7 +62,8 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
|
||||
_reader.Read();
|
||||
return new MarkupToken(MarkupTokenKind.Close, string.Empty, position);
|
||||
Current = new MarkupToken(MarkupTokenKind.Close, string.Empty, position);
|
||||
return true;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
@@ -80,7 +84,8 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
|
||||
_reader.Read();
|
||||
return new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position);
|
||||
Current = new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -97,7 +102,8 @@ namespace Spectre.Console.Internal
|
||||
builder.Append(_reader.Read());
|
||||
}
|
||||
|
||||
return new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position);
|
||||
Current = new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user