mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98cf63f485 | ||
|
|
c3286a4842 | ||
|
|
e5bf2bd498 | ||
|
|
5267ebda49 | ||
|
|
f19202b427 | ||
|
|
8e4f33bba4 |
@@ -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()
|
||||
|
||||
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
|
||||
@@ -9,11 +9,11 @@ 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("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height);
|
||||
AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
@@ -40,17 +40,44 @@ 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("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), content: Justify.Center)))));
|
||||
|
||||
// Reset colors
|
||||
AnsiConsole.ResetColors();
|
||||
|
||||
// Left adjusted panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
Text.New("Left adjusted\nLeft",
|
||||
foreground: Color.White),
|
||||
fit: true));
|
||||
|
||||
// Centered panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
Text.New("Centered\nCenter",
|
||||
foreground: Color.White),
|
||||
fit: true, content: Justify.Center));
|
||||
|
||||
// Right adjusted panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
Text.New("Right adjusted\nRight",
|
||||
foreground: Color.White),
|
||||
fit: true, content: Justify.Right));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
Ansi = ansi,
|
||||
ColorSystem = (ColorSystemSupport)system,
|
||||
Out = _writer,
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Spectre.Console.Tests/Fixtures/PlainConsole.cs
Normal file
42
src/Spectre.Console.Tests/Fixtures/PlainConsole.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
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 => throw new NotSupportedException();
|
||||
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)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Encoding = encoding ?? Encoding.UTF8;
|
||||
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");
|
||||
240
src/Spectre.Console.Tests/Unit/ColorTests.cs
Normal file
240
src/Spectre.Console.Tests/Unit/ColorTests.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs
Normal file
148
src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
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_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 📦"),
|
||||
content: Justify.Center);
|
||||
|
||||
// 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_Fit_Panel_To_Parent_If_Enabled()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 25);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New("Hello World"), fit: 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_Right()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 25);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Right));
|
||||
|
||||
// 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"), fit: true, content: Justify.Center));
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Background_Is_Set_Twice()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("green on blue yellow", out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Color_Name_Could_Not_Be_Found()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("bold lol", out var style);
|
||||
|
||||
// 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 var style);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets the console's capabilities.
|
||||
/// </summary>
|
||||
public static AnsiConsoleCapabilities Capabilities => Console.Capabilities;
|
||||
public static Capabilities Capabilities => Console.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer width of the console.
|
||||
@@ -63,12 +63,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 +83,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 +91,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()
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Represents console capabilities.
|
||||
/// </summary>
|
||||
public sealed class AnsiConsoleCapabilities
|
||||
public sealed class Capabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
@@ -16,7 +16,7 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public ColorSystem ColorSystem { get; }
|
||||
|
||||
internal AnsiConsoleCapabilities(bool supportsAnsi, ColorSystem colorSystem)
|
||||
internal Capabilities(bool supportsAnsi, ColorSystem colorSystem)
|
||||
{
|
||||
SupportsAnsi = supportsAnsi;
|
||||
ColorSystem = colorSystem;
|
||||
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,7 @@ namespace Spectre.Console
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Color other)
|
||||
{
|
||||
return Number == other.Number || (R == other.R && G == other.G && B == other.B);
|
||||
return R == other.R && G == other.G && B == other.B;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,6 +107,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 +126,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 +176,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 +214,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/Spectre.Console/Composition/IRenderable.cs
Normal file
27
src/Spectre.Console/Composition/IRenderable.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
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="encoding">The encoding to use.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>The width of the object.</returns>
|
||||
int Measure(Encoding encoding, int maxWidth);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the object.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding to use.</param>
|
||||
/// <param name="width">The width of the render area.</param>
|
||||
/// <returns>A collection of segments.</returns>
|
||||
IEnumerable<Segment> Render(Encoding encoding, int width);
|
||||
}
|
||||
}
|
||||
119
src/Spectre.Console/Composition/Panel.cs
Normal file
119
src/Spectre.Console/Composition/Panel.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
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;
|
||||
private readonly bool _fit;
|
||||
private readonly Justify _content;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Panel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="child">The child.</param>
|
||||
/// <param name="fit">Whether or not to fit the panel to it's parent.</param>
|
||||
/// <param name="content">The justification of the panel content.</param>
|
||||
public Panel(IRenderable child, bool fit = false, Justify content = Justify.Left)
|
||||
{
|
||||
_child = child;
|
||||
_fit = fit;
|
||||
_content = content;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var childWidth = _child.Measure(encoding, maxWidth);
|
||||
return childWidth + 4;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
{
|
||||
var childWidth = width - 4;
|
||||
if (!_fit)
|
||||
{
|
||||
childWidth = _child.Measure(encoding, width - 2);
|
||||
}
|
||||
|
||||
var result = new List<Segment>();
|
||||
var panelWidth = childWidth + 2;
|
||||
|
||||
result.Add(new Segment("┌"));
|
||||
result.Add(new Segment(new string('─', panelWidth)));
|
||||
result.Add(new Segment("┐"));
|
||||
result.Add(new Segment("\n"));
|
||||
|
||||
// Render the child.
|
||||
var childSegments = _child.Render(encoding, childWidth);
|
||||
|
||||
// Split the child segments into lines.
|
||||
var lines = Segment.SplitLines(childSegments, childWidth);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
result.Add(new Segment("│ "));
|
||||
|
||||
var content = new List<Segment>();
|
||||
|
||||
var length = line.Sum(segment => segment.CellLength(encoding));
|
||||
if (length < childWidth)
|
||||
{
|
||||
if (_content == Justify.Right)
|
||||
{
|
||||
var diff = childWidth - length;
|
||||
content.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
else if (_content == Justify.Center)
|
||||
{
|
||||
var diff = (childWidth - length) / 2;
|
||||
content.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var segment in line)
|
||||
{
|
||||
content.Add(segment.StripLineEndings());
|
||||
}
|
||||
|
||||
if (length < childWidth)
|
||||
{
|
||||
if (_content == Justify.Left)
|
||||
{
|
||||
var diff = childWidth - length;
|
||||
content.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
else if (_content == Justify.Center)
|
||||
{
|
||||
var diff = (childWidth - length) / 2;
|
||||
content.Add(new Segment(new string(' ', diff)));
|
||||
|
||||
var remainder = (childWidth - length) % 2;
|
||||
if (remainder != 0)
|
||||
{
|
||||
content.Add(new Segment(new string(' ', remainder)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.AddRange(content);
|
||||
|
||||
result.Add(new Segment(" │"));
|
||||
result.Add(new Segment("\n"));
|
||||
}
|
||||
|
||||
result.Add(new Segment("└"));
|
||||
result.Add(new Segment(new string('─', panelWidth)));
|
||||
result.Add(new Segment("┘"));
|
||||
result.Add(new Segment("\n"));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/Spectre.Console/Composition/Segment.cs
Normal file
215
src/Spectre.Console/Composition/Segment.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
216
src/Spectre.Console/Composition/Text.cs
Normal file
216
src/Spectre.Console/Composition/Text.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
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")]
|
||||
public sealed class Text : IRenderable
|
||||
{
|
||||
private readonly List<Span> _spans;
|
||||
private string _text;
|
||||
|
||||
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 ?? 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>
|
||||
/// 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/>
|
||||
public int Measure(Encoding encoding, int maxWidth)
|
||||
{
|
||||
var lines = _text.SplitLines();
|
||||
return lines.Max(x => x.CellLength(encoding));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(Encoding encoding, int width)
|
||||
{
|
||||
var result = new List<Segment>();
|
||||
|
||||
var segments = SplitLineBreaks(CreateSegments());
|
||||
|
||||
foreach (var (_, _, last, line) in Segment.SplitLines(segments, width).Enumerate())
|
||||
{
|
||||
foreach (var segment in line)
|
||||
{
|
||||
result.Add(segment.StripLineEndings());
|
||||
}
|
||||
|
||||
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 Queue<Segment>(segments);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var segment = queue.Dequeue();
|
||||
|
||||
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.Enqueue(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.Add((0, false, 0));
|
||||
spans.AddRange(_spans.SelectIndex((span, index) => (span.Start, false, index + 1)));
|
||||
spans.AddRange(_spans.SelectIndex((span, index) => (span.End, true, index + 1)));
|
||||
spans.Add((_text.Length, true, 0));
|
||||
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>
|
||||
|
||||
45
src/Spectre.Console/ConsoleExtensions.Rendering.cs
Normal file
45
src/Spectre.Console/ConsoleExtensions.Rendering.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
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));
|
||||
}
|
||||
|
||||
foreach (var segment in renderable.Render(console.Encoding, 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,7 +32,7 @@ namespace Spectre.Console.Internal
|
||||
new Regex("bvterm"), // Bitvise SSH Client
|
||||
};
|
||||
|
||||
public static bool SupportsAnsi(bool upgrade)
|
||||
public static bool Detect(bool upgrade)
|
||||
{
|
||||
// Github action doesn't setup a correct PTY but supports ANSI.
|
||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
|
||||
@@ -57,7 +57,7 @@ namespace Spectre.Console.Internal
|
||||
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;
|
||||
}
|
||||
@@ -71,8 +71,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;
|
||||
|
||||
@@ -94,7 +96,7 @@ namespace Spectre.Console.Internal
|
||||
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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -44,24 +46,11 @@ namespace Spectre.Console.Internal
|
||||
_out = @out ?? throw new ArgumentNullException(nameof(@out));
|
||||
_system = system;
|
||||
|
||||
Capabilities = new AnsiConsoleCapabilities(true, system);
|
||||
Capabilities = new Capabilities(true, system);
|
||||
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)
|
||||
@@ -73,8 +62,8 @@ namespace Spectre.Console.Internal
|
||||
|
||||
_out.Write(AnsiBuilder.GetAnsi(
|
||||
_system,
|
||||
text,
|
||||
Style,
|
||||
text.NormalizeLineEndings(native: true),
|
||||
Decoration,
|
||||
Foreground,
|
||||
Background));
|
||||
}
|
||||
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)
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Spectre.Console.Internal
|
||||
var buffer = settings.Out ?? System.Console.Out;
|
||||
|
||||
var supportsAnsi = settings.Ansi == AnsiSupport.Detect
|
||||
? AnsiDetector.SupportsAnsi(true)
|
||||
? AnsiDetector.Detect(true)
|
||||
: settings.Ansi == AnsiSupport.Yes;
|
||||
|
||||
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
|
||||
@@ -25,7 +25,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
return new AnsiConsoleRenderer(buffer, colorSystem)
|
||||
{
|
||||
Style = Styles.None,
|
||||
Decoration = Decoration.None,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class EnumerableExtensions
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/Spectre.Console/Internal/Extensions/StringExtensions.cs
Normal file
36
src/Spectre.Console/Internal/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -87,23 +90,27 @@ namespace Spectre.Console.Internal
|
||||
_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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/Spectre.Console/Internal/Text/Markup/MarkupParser.cs
Normal file
55
src/Spectre.Console/Internal/Text/Markup/MarkupParser.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
104
src/Spectre.Console/Internal/Text/StyleParser.cs
Normal file
104
src/Spectre.Console/Internal/Text/StyleParser.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class StyleParser
|
||||
{
|
||||
public static Style Parse(string text)
|
||||
{
|
||||
var style = Parse(text, out var error);
|
||||
if (error != null)
|
||||
{
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
public static bool TryParse(string text, out Style style)
|
||||
{
|
||||
style = Parse(text, out var error);
|
||||
if (error != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Style Parse(string text, out string error)
|
||||
{
|
||||
var effectiveDecoration = (Decoration?)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("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreground = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var decoration = DecorationTable.GetDecoration(part);
|
||||
if (decoration != null)
|
||||
{
|
||||
if (effectiveDecoration == null)
|
||||
{
|
||||
effectiveDecoration = Decoration.None;
|
||||
}
|
||||
|
||||
effectiveDecoration |= decoration.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var color = ColorTable.GetColor(part);
|
||||
if (color == null)
|
||||
{
|
||||
if (!foreground)
|
||||
{
|
||||
error = $"Could not find color '{part}'.";
|
||||
}
|
||||
else
|
||||
{
|
||||
error = $"Could not find color or style '{part}'.";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (foreground)
|
||||
{
|
||||
if (effectiveForeground != null)
|
||||
{
|
||||
error = "A foreground color has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveForeground = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectiveBackground != null)
|
||||
{
|
||||
error = "A background color has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveBackground = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error = null;
|
||||
return new Style(effectiveForeground, effectiveBackground, effectiveDecoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Spectre.Console/Justify.cs
Normal file
23
src/Spectre.Console/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,
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="**/ColorPalette.*.cs">
|
||||
<DependentUpon>**/ColorPalette.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Color.*.cs">
|
||||
<DependentUpon>Color.cs</DependentUpon>
|
||||
</Compile>
|
||||
@@ -18,6 +21,7 @@
|
||||
<Compile Update="ConsoleExtensions.*.cs">
|
||||
<DependentUpon>ConsoleExtensions.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
134
src/Spectre.Console/Style.cs
Normal file
134
src/Spectre.Console/Style.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents color and text decoration.
|
||||
/// </summary>
|
||||
public sealed class Style : IEquatable<Style>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the foreground color.
|
||||
/// </summary>
|
||||
public Color Foreground { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the background color.
|
||||
/// </summary>
|
||||
public Color Background { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text decoration.
|
||||
/// </summary>
|
||||
public Decoration Decoration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="Style"/> with the
|
||||
/// default colors and without text decoration.
|
||||
/// </summary>
|
||||
public static Style Plain { get; } = new Style();
|
||||
|
||||
private Style()
|
||||
: this(null, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Style"/> class.
|
||||
/// </summary>
|
||||
/// <param name="foreground">The foreground color.</param>
|
||||
/// <param name="background">The background color.</param>
|
||||
/// <param name="decoration">The text decoration.</param>
|
||||
public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null)
|
||||
{
|
||||
Foreground = foreground ?? Color.Default;
|
||||
Background = background ?? Color.Default;
|
||||
Decoration = decoration ?? Decoration.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
|
||||
/// </summary>
|
||||
/// <param name="text">A string containing a style to parse.</param>
|
||||
/// <returns>A <see cref="Style"/> equivalent of the text contained in <paramref name="text"/>.</returns>
|
||||
public static Style Parse(string text)
|
||||
{
|
||||
return StyleParser.Parse(text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
|
||||
/// A return value indicates whether the operation succeeded.
|
||||
/// </summary>
|
||||
/// <param name="text">A string containing a style to parse.</param>
|
||||
/// <param name="result">
|
||||
/// When this method returns, contains the <see cref="Style"/> equivalent of the text contained in <paramref name="text"/>,
|
||||
/// if the conversion succeeded, or <c>null</c> if the conversion failed.
|
||||
/// </param>
|
||||
/// <returns><c>true</c> if s was converted successfully; otherwise, <c>false</c>.</returns>
|
||||
public static bool TryParse(string text, out Style result)
|
||||
{
|
||||
return StyleParser.TryParse(text, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines this style with another one.
|
||||
/// </summary>
|
||||
/// <param name="other">The item to combine with this.</param>
|
||||
/// <returns>A new style representing a combination of this and the other one.</returns>
|
||||
public Style Combine(Style other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(other));
|
||||
}
|
||||
|
||||
var foreground = Foreground;
|
||||
if (!other.Foreground.IsDefault)
|
||||
{
|
||||
foreground = other.Foreground;
|
||||
}
|
||||
|
||||
var background = Background;
|
||||
if (!other.Background.IsDefault)
|
||||
{
|
||||
background = other.Background;
|
||||
}
|
||||
|
||||
return new Style(foreground, background, Decoration | other.Decoration);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Foreground.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Background.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Decoration.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as Style);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Style other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Foreground.Equals(other.Foreground) &&
|
||||
Background.Equals(other.Background) &&
|
||||
Decoration == other.Decoration;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user