Compare commits

...

6 Commits
0.3.0 ... 0.4.0

Author SHA1 Message Date
Patrik Svensson
98cf63f485 Rename Style and Appearance
* Renames Style -> Decoration
* Renames Appearance -> Style
* Adds Style.Parse and Style.TryParse
2020-08-03 23:30:47 +02:00
Patrik Svensson
c3286a4842 Rename folder 2020-08-03 15:34:33 +02:00
Patrik Svensson
e5bf2bd498 Autogenerate known colors and palettes
This will make it a bit easier to make changes
2020-08-03 15:22:39 +02:00
Patrik Svensson
5267ebda49 Get color names from lookup table
Also adds tests for Color struct and fixes a bug
that had to do with equality.
2020-08-03 11:32:17 +02:00
Patrik Svensson
f19202b427 Improve text composite
- A `Text` object should not be able to justify itself.
  All justification needs to be done by a parent.
- Apply colors and styles to part of a `Text` object
- Markup parser should return a `Text` object
2020-08-02 22:45:01 +02:00
Patrik Svensson
8e4f33bba4 Added initial support for rendering composites
This is far from complete, but it's a start
and it will enable us to create things like tables
and other complex objects in the long run.
2020-07-30 22:55:42 +02:00
78 changed files with 7569 additions and 826 deletions

View File

@@ -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`,
@@ -64,7 +64,10 @@ you can access it via `AnsiConsole.Console`.
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.
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(

2
scripts/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
Generated
Temp

View 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"

View 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; }
}
}

File diff suppressed because it is too large Load Diff

View 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>

View 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

View 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; }
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Generator.Models
{
public sealed class Palette
{
}
}

View 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);
}
}
}

View 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 ~}}
}
}

View 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 ~}}
};
}
}
}

View 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 ~}}
};
}
}
}

View File

@@ -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

View File

@@ -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));
}
}
}
}

View File

@@ -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

View File

@@ -1,47 +0,0 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests
{
public partial class AnsiConsoleTests
{
[Theory]
[InlineData(Styles.Bold, "\u001b[1mHello World")]
[InlineData(Styles.Dim, "\u001b[2mHello World")]
[InlineData(Styles.Italic, "\u001b[3mHello World")]
[InlineData(Styles.Underline, "\u001b[4mHello World")]
[InlineData(Styles.Invert, "\u001b[7mHello World")]
[InlineData(Styles.Conceal, "\u001b[8mHello World")]
[InlineData(Styles.SlowBlink, "\u001b[5mHello World")]
[InlineData(Styles.RapidBlink, "\u001b[6mHello World")]
[InlineData(Styles.Strikethrough, "\u001b[9mHello World")]
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")]
[InlineData(Styles.Bold | Styles.Underline | Styles.Conceal, "\u001b[1;4;8mHello World")]
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);
}
}
}

View 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);
}
}
}

View File

@@ -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()

View 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);
}
}
}

View 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);
}
}
}

View File

@@ -1,7 +1,7 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{

View File

@@ -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
{

View 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")]
[InlineData(Decoration.Dim, "\u001b[2mHello World")]
[InlineData(Decoration.Italic, "\u001b[3mHello World")]
[InlineData(Decoration.Underline, "\u001b[4mHello World")]
[InlineData(Decoration.Invert, "\u001b[7mHello World")]
[InlineData(Decoration.Conceal, "\u001b[8mHello World")]
[InlineData(Decoration.SlowBlink, "\u001b[5mHello World")]
[InlineData(Decoration.RapidBlink, "\u001b[6mHello World")]
[InlineData(Decoration.Strikethrough, "\u001b[9mHello World")]
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")]
[InlineData(Decoration.Bold | Decoration.Underline | Decoration.Conceal, "\u001b[1;4;8mHello World")]
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);
}
}
}

View File

@@ -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");

View 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)");
}
}
}
}

View 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("└─────────────────┘");
}
}
}

View 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");
}
}
}
}

View 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("Hello World");
}
[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("Hello\n Worl\nd");
}
}
}
}

View 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();
}
}
}
}

View 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);
}
}
}

View File

@@ -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()
{

View File

@@ -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;

View File

@@ -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);
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}

View 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));
}
}
}

View File

@@ -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>

View 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);
}
}
}
}
}

View File

@@ -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)

View File

@@ -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,

View File

@@ -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.

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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));
}

View 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,
};
}
}
}

View File

@@ -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)

View 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 },
};
}
}
}

View 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;
}
}
}

View File

@@ -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,
};
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View 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);
}
}
}

View 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,
}
}

View File

@@ -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>

View 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;
}
}
}