mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7bbaf4a85 | ||
|
|
0119364728 | ||
|
|
1d74fb909c | ||
|
|
5d132220ba | ||
|
|
a273f74758 | ||
|
|
717931f11c | ||
|
|
bcfc495843 | ||
|
|
9aa36c4cf0 | ||
|
|
22d4af4482 | ||
|
|
f4d1796e40 | ||
|
|
2dd0eb9f74 | ||
|
|
fa85216554 | ||
|
|
d475e3b30a | ||
|
|
9637066927 | ||
|
|
0b4321115a | ||
|
|
5cd9ece31a | ||
|
|
b0341862cf | ||
|
|
2e7b3d520a | ||
|
|
646f51a628 | ||
|
|
a0bd481255 | ||
|
|
6d197c5140 | ||
|
|
108e56c229 | ||
|
|
66994cd904 | ||
|
|
f9bd936254 | ||
|
|
a068fc68c3 | ||
|
|
aa34c145b9 | ||
|
|
98cf63f485 | ||
|
|
c3286a4842 | ||
|
|
e5bf2bd498 | ||
|
|
5267ebda49 | ||
|
|
f19202b427 | ||
|
|
8e4f33bba4 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -2,9 +2,7 @@ name: Continuous Integration
|
||||
on: pull_request
|
||||
|
||||
env:
|
||||
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
# Disable sending usage data to Microsoft
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
jobs:
|
||||
@@ -28,10 +26,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Git tags'
|
||||
run: git fetch --tags
|
||||
shell: bash
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
|
||||
10
.github/workflows/publish.yaml
vendored
10
.github/workflows/publish.yaml
vendored
@@ -8,9 +8,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
# Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
# Disable sending usage data to Microsoft
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
jobs:
|
||||
@@ -39,10 +37,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Git tags'
|
||||
run: git fetch --tags
|
||||
shell: bash
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
@@ -69,10 +63,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Get Git tags'
|
||||
run: git fetch --tags
|
||||
shell: bash
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
|
||||
52
README.md
52
README.md
@@ -1,6 +1,6 @@
|
||||
# `Spectre.Console`
|
||||
|
||||
_[](https://www.nuget.org/packages/spectre.console)_
|
||||
_[](https://www.nuget.org/packages/spectre.console)_
|
||||
|
||||
A .NET Standard 2.0 library that makes it easier to create beautiful console applications.
|
||||
It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)
|
||||
@@ -13,8 +13,9 @@ for Python.
|
||||
3. [Usage](#usage)
|
||||
3.1. [Using the static API](#using-the-static-api)
|
||||
3.2. [Creating a console](#creating-a-console)
|
||||
4. [Available styles](#available-styles)
|
||||
5. [Predefined colors](#predefined-colors)
|
||||
4. [Running examples](#running-examples)
|
||||
5. [Available styles](#available-styles)
|
||||
6. [Predefined colors](#predefined-colors)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -50,11 +51,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 +65,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(
|
||||
@@ -81,6 +85,42 @@ when manually creating a console, remember that the user's terminal
|
||||
might not be able to use it, so unless you're creating an IAnsiConsole
|
||||
for testing, always use `ColorSystemSupport.Detect` and `AnsiSupport.Detect`._
|
||||
|
||||
## Running examples
|
||||
|
||||
To see Spectre.Console in action, install the
|
||||
[dotnet-example](https://github.com/patriksvensson/dotnet-example)
|
||||
global tool.
|
||||
|
||||
```
|
||||
> dotnet tool install -g dotnet-example
|
||||
```
|
||||
|
||||
Now you can list available examples in this repository:
|
||||
|
||||
```
|
||||
> dotnet example
|
||||
|
||||
Examples
|
||||
|
||||
Colors Demonstrates how to use colors in the console.
|
||||
Grid Demonstrates how to render grids in a console.
|
||||
Panel Demonstrates how to render items in panels.
|
||||
Table Demonstrates how to render tables in a console.
|
||||
```
|
||||
|
||||
And to run an example:
|
||||
|
||||
```
|
||||
> dotnet example table
|
||||
┌──────────┬──────────┬────────┐
|
||||
│ Foo │ Bar │ Baz │
|
||||
├──────────┼──────────┼────────┤
|
||||
│ Hello │ World! │ │
|
||||
│ Bounjour │ le │ monde! │
|
||||
│ Hej │ Världen! │ │
|
||||
└──────────┴──────────┴────────┘
|
||||
```
|
||||
|
||||
## Available styles
|
||||
|
||||
_NOTE: Not all styles are supported in every terminal._
|
||||
|
||||
14
examples/Colors/Colors.csproj
Normal file
14
examples/Colors/Colors.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Description>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
78
examples/Colors/Program.cs
Normal file
78
examples/Colors/Program.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace ColorExample
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// 4-BIT
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
AnsiConsole.ResetColors();
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[bold underline]4-bit Colors[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
AnsiConsole.Background = Color.FromInt32(i);
|
||||
AnsiConsole.Write(string.Format(" {0,-9}", AnsiConsole.Background.ToString()));
|
||||
AnsiConsole.ResetColors();
|
||||
if ((i + 1) % 8 == 0)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// 8-BIT
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
AnsiConsole.ResetColors();
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[bold underline]8-bit Colors[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
for (var j = 0; j < 16; j++)
|
||||
{
|
||||
var number = i * 16 + j;
|
||||
AnsiConsole.Background = Color.FromInt32(number);
|
||||
AnsiConsole.Write(string.Format(" {0,-4}", number));
|
||||
AnsiConsole.ResetColors();
|
||||
if ((number + 1) % 16 == 0)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// 24-BIT
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
AnsiConsole.ResetColors();
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("[bold underline]24-bit Colors[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
var index = 0;
|
||||
for (var i = 0.0005; i < 1; i += 0.0025)
|
||||
{
|
||||
index++;
|
||||
|
||||
var color = Utilities.HSL2RGB(i, 0.5, 0.5);
|
||||
AnsiConsole.Background = new Color(color.R, color.G, color.B);
|
||||
AnsiConsole.Write(" ");
|
||||
|
||||
if (index % 50 == 0)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
examples/Colors/Utilities.cs
Normal file
77
examples/Colors/Utilities.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace ColorExample
|
||||
{
|
||||
public static class Utilities
|
||||
{
|
||||
// Borrowed from https://geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
|
||||
public static Color HSL2RGB(double h, double sl, double l)
|
||||
{
|
||||
double v;
|
||||
double r, g, b;
|
||||
|
||||
r = l; // default to gray
|
||||
g = l;
|
||||
b = l;
|
||||
v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);
|
||||
|
||||
if (v > 0)
|
||||
{
|
||||
double m;
|
||||
double sv;
|
||||
int sextant;
|
||||
double fract, vsf, mid1, mid2;
|
||||
|
||||
m = l + l - v;
|
||||
sv = (v - m) / v;
|
||||
h *= 6.0;
|
||||
|
||||
sextant = (int)h;
|
||||
fract = h - sextant;
|
||||
vsf = v * sv * fract;
|
||||
mid1 = m + vsf;
|
||||
mid2 = v - vsf;
|
||||
|
||||
switch (sextant)
|
||||
{
|
||||
case 0:
|
||||
r = v;
|
||||
g = mid1;
|
||||
b = m;
|
||||
break;
|
||||
case 1:
|
||||
r = mid2;
|
||||
g = v;
|
||||
b = m;
|
||||
break;
|
||||
case 2:
|
||||
r = m;
|
||||
g = v;
|
||||
b = mid1;
|
||||
break;
|
||||
case 3:
|
||||
r = m;
|
||||
g = mid2;
|
||||
b = v;
|
||||
break;
|
||||
case 4:
|
||||
r = mid1;
|
||||
g = m;
|
||||
b = v;
|
||||
break;
|
||||
case 5:
|
||||
r = v;
|
||||
g = m;
|
||||
b = mid2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Color(
|
||||
Convert.ToByte(r * 255.0f),
|
||||
Convert.ToByte(g * 255.0f),
|
||||
Convert.ToByte(b * 255.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
14
examples/Grid/Grid.csproj
Normal file
14
examples/Grid/Grid.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Description>Demonstrates how to render grids in a console.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
26
examples/Grid/Program.cs
Normal file
26
examples/Grid/Program.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace GridExample
|
||||
{
|
||||
public sealed class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 });
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Options:", "", "");
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for.");
|
||||
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the [grey]MSBuild[/] verbosity level.");
|
||||
|
||||
AnsiConsole.Render(grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Description>Demonstrates how to render items in panels.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
|
||||
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
50
examples/Panel/Program.cs
Normal file
50
examples/Panel/Program.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace PanelExample
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var content = Text.Markup(
|
||||
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
|
||||
"So I put a 📦 in a 📦\n\n" +
|
||||
"😅");
|
||||
|
||||
AnsiConsole.Render(
|
||||
new Panel(
|
||||
new Panel(content)
|
||||
{
|
||||
Alignment = Justify.Center,
|
||||
Border = BorderKind.Rounded
|
||||
}));
|
||||
|
||||
// Left adjusted panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
new Text("Left adjusted\nLeft"))
|
||||
{
|
||||
Expand = true,
|
||||
Alignment = Justify.Left,
|
||||
});
|
||||
|
||||
// Centered ASCII panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
new Text("Centered\nCenter"))
|
||||
{
|
||||
Expand = true,
|
||||
Alignment = Justify.Center,
|
||||
Border = BorderKind.Ascii,
|
||||
});
|
||||
|
||||
// Right adjusted, rounded panel with text
|
||||
AnsiConsole.Render(new Panel(
|
||||
new Text("Right adjusted\nRight"))
|
||||
{
|
||||
Expand = true,
|
||||
Alignment = Justify.Right,
|
||||
Border = BorderKind.Rounded,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
55
examples/Table/Program.cs
Normal file
55
examples/Table/Program.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace TableExample
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// A simple table§
|
||||
RenderSimpleTable();
|
||||
|
||||
// A big table
|
||||
RenderBigTable();
|
||||
}
|
||||
|
||||
private static void RenderSimpleTable()
|
||||
{
|
||||
// Create the table.
|
||||
var table = new Table();
|
||||
table.AddColumn(new TableColumn("[u]Foo[/]"));
|
||||
table.AddColumn(new TableColumn("[u]Bar[/]"));
|
||||
table.AddColumn(new TableColumn("[u]Baz[/]"));
|
||||
|
||||
// Add some rows
|
||||
table.AddRow("Hello", "[red]World![/]", "");
|
||||
table.AddRow("[blue]Bounjour[/]", "[white]le[/]", "[red]monde![/]");
|
||||
table.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
|
||||
|
||||
AnsiConsole.Render(table);
|
||||
}
|
||||
|
||||
private static void RenderBigTable()
|
||||
{
|
||||
// Create the table.
|
||||
var table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumn("[red underline]Foo[/]");
|
||||
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true });
|
||||
|
||||
// Add some rows
|
||||
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
|
||||
table.AddRow("[yellow]Patrik [green]\"Hello World\"[/] Svensson[/]", "Was [underline]here[/]!");
|
||||
table.AddEmptyRow();
|
||||
table.AddRow(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " +
|
||||
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
|
||||
"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " +
|
||||
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language");
|
||||
table.AddEmptyRow();
|
||||
table.AddRow("Hej 👋", "[green]Världen[/]");
|
||||
|
||||
AnsiConsole.Render(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
examples/Table/Table.csproj
Normal file
14
examples/Table/Table.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Description>Demonstrates how to render tables in a console.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
BIN
gfx/large-logo.png
Normal file
BIN
gfx/large-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 KiB |
BIN
gfx/medium-logo.png
Normal file
BIN
gfx/medium-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
gfx/small-logo.png
Normal file
BIN
gfx/small-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
2
scripts/.gitignore
vendored
Normal file
2
scripts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Generated
|
||||
Temp
|
||||
24
scripts/Generate-Colors.ps1
Normal file
24
scripts/Generate-Colors.ps1
Normal file
@@ -0,0 +1,24 @@
|
||||
##########################################################
|
||||
# Script that generates known colors and lookup tables.
|
||||
##########################################################
|
||||
|
||||
$Output = Join-Path $PSScriptRoot "Temp"
|
||||
$Source = Join-Path $PSScriptRoot "/../src/Spectre.Console"
|
||||
|
||||
if(!(Test-Path $Output -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path $Output | Out-Null
|
||||
}
|
||||
|
||||
# Generate the files
|
||||
Push-Location Generator
|
||||
&dotnet run -- colors "$Output"
|
||||
if(!$?) {
|
||||
Pop-Location
|
||||
Throw "An error occured when generating code."
|
||||
}
|
||||
Pop-Location
|
||||
|
||||
# Copy the files to the correct location
|
||||
Copy-Item (Join-Path "$Output" "Color.Generated.cs") -Destination "$Source/Color.Generated.cs"
|
||||
Copy-Item (Join-Path "$Output" "ColorPalette.Generated.cs") -Destination "$Source/Internal/Colors/ColorPalette.Generated.cs"
|
||||
Copy-Item (Join-Path "$Output" "ColorTable.Generated.cs") -Destination "$Source/Internal/Colors/ColorTable.Generated.cs"
|
||||
59
scripts/Generator/Commands/ColorGeneratorCommand.cs
Normal file
59
scripts/Generator/Commands/ColorGeneratorCommand.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Generator.Models;
|
||||
using Scriban;
|
||||
using Spectre.Cli;
|
||||
using Spectre.IO;
|
||||
|
||||
namespace Generator.Commands
|
||||
{
|
||||
public sealed class ColorGeneratorCommand : Command<GeneratorCommandSettings>
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public ColorGeneratorCommand()
|
||||
{
|
||||
_fileSystem = new FileSystem();
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context, GeneratorCommandSettings settings)
|
||||
{
|
||||
var templates = new FilePath[]
|
||||
{
|
||||
"Templates/ColorPalette.Generated.template",
|
||||
"Templates/Color.Generated.template",
|
||||
"Templates/ColorTable.Generated.template"
|
||||
};
|
||||
|
||||
// Read the color model.
|
||||
var model = Color.Parse(File.ReadAllText("Data/colors.json"));
|
||||
|
||||
var output = new DirectoryPath(settings.Output);
|
||||
if (!_fileSystem.Directory.Exists(settings.Output))
|
||||
{
|
||||
_fileSystem.Directory.Create(settings.Output);
|
||||
}
|
||||
|
||||
foreach (var templatePath in templates)
|
||||
{
|
||||
// Parse the Scriban template.
|
||||
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
|
||||
|
||||
// Render the template with the model.
|
||||
var result = template.Render(new { Colors = model });
|
||||
|
||||
// Write output to file
|
||||
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
|
||||
File.WriteAllText(file.FullPath, result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GeneratorCommandSettings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<OUTPUT>")]
|
||||
public string Output { get; set; }
|
||||
}
|
||||
}
|
||||
3842
scripts/Generator/Data/colors.json
Normal file
3842
scripts/Generator/Data/colors.json
Normal file
File diff suppressed because it is too large
Load Diff
36
scripts/Generator/Generator.csproj
Normal file
36
scripts/Generator/Generator.csproj
Normal file
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="out\**" />
|
||||
<EmbeddedResource Remove="out\**" />
|
||||
<None Remove="out\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Data\colors.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\ColorTable.Generated.template">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\Color.Generated.template">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\ColorPalette.Generated.template">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Scriban" Version="2.1.3" />
|
||||
<PackageReference Include="Spectre.Cli" Version="0.36.1-preview.0.6" />
|
||||
<PackageReference Include="Spectre.IO" Version="0.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
37
scripts/Generator/Generator.sln
Normal file
37
scripts/Generator/Generator.sln
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30320.27
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator.csproj", "{5668D267-53E3-4B99-97AE-59AA597D22ED}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.Build.0 = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5F37FDE3-D591-4D43-8DDE-2ED6BAB0A7B4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
62
scripts/Generator/Models/Color.cs
Normal file
62
scripts/Generator/Models/Color.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Generator.Models
|
||||
{
|
||||
public sealed class Color
|
||||
{
|
||||
public int Number { get; set; }
|
||||
public string Hex { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Rgb Rgb { get; set; }
|
||||
|
||||
public int R => Rgb.R;
|
||||
public int G => Rgb.G;
|
||||
public int B => Rgb.B;
|
||||
|
||||
public static IEnumerable<Color> Parse(string json)
|
||||
{
|
||||
var source = JsonConvert.DeserializeObject<List<Color>>(json);
|
||||
|
||||
var check = new Dictionary<string, Color>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var color in source.OrderBy(c => c.Number))
|
||||
{
|
||||
if (!check.ContainsKey(color.Name))
|
||||
{
|
||||
check.Add(color.Name, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newName = (string)null;
|
||||
for (int i = 1; i < 100; i++)
|
||||
{
|
||||
if (!check.ContainsKey($"{color.Name}_{i}"))
|
||||
{
|
||||
newName = $"{color.Name}_{i}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newName == null)
|
||||
{
|
||||
throw new InvalidOperationException("Impossible!");
|
||||
}
|
||||
|
||||
check.Add(newName, color);
|
||||
color.Name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Rgb
|
||||
{
|
||||
public int R { get; set; }
|
||||
public int G { get; set; }
|
||||
public int B { get; set; }
|
||||
}
|
||||
}
|
||||
14
scripts/Generator/Models/ColorModel.cs
Normal file
14
scripts/Generator/Models/ColorModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Generator.Models
|
||||
{
|
||||
public sealed class ColorModel
|
||||
{
|
||||
public List<Color> Colors { get; set; }
|
||||
|
||||
public ColorModel(IEnumerable<Color> colors)
|
||||
{
|
||||
Colors = new List<Color>(colors);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
scripts/Generator/Models/Palette.cs
Normal file
6
scripts/Generator/Models/Palette.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Generator.Models
|
||||
{
|
||||
public sealed class Palette
|
||||
{
|
||||
}
|
||||
}
|
||||
19
scripts/Generator/Program.cs
Normal file
19
scripts/Generator/Program.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Generator.Commands;
|
||||
using Spectre.Cli;
|
||||
|
||||
namespace Generator
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp();
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.AddCommand<ColorGeneratorCommand>("colors");
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
scripts/Generator/Templates/Color.Generated.template
Normal file
36
scripts/Generator/Templates/Color.Generated.template
Normal file
@@ -0,0 +1,36 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a color.
|
||||
/// </summary>
|
||||
public partial struct Color
|
||||
{
|
||||
internal Color(byte number, byte red, byte green, byte blue, bool isDefault = false)
|
||||
: this(red, green, blue)
|
||||
{
|
||||
Number = number;
|
||||
IsDefault = isDefault;
|
||||
}
|
||||
{{~ for color in colors }}
|
||||
/// <summary>
|
||||
/// Gets the color "{{ color.name }}" (RGB {{ color.r }},{{ color.g }},{{ color.b }}).
|
||||
/// </summary>
|
||||
{{- if string.contains color.name "_" }}
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
|
||||
{{- end}}
|
||||
public static Color {{ color.name }} { get; } = new Color({{ color.number }}, {{ color.r }}, {{ color.g }}, {{ color.b }});
|
||||
{{~ end ~}}
|
||||
}
|
||||
}
|
||||
47
scripts/Generator/Templates/ColorPalette.Generated.template
Normal file
47
scripts/Generator/Templates/ColorPalette.Generated.template
Normal file
@@ -0,0 +1,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
private static List<Color> GenerateLegacyPalette()
|
||||
{
|
||||
return new List<Color>
|
||||
{
|
||||
{{~ for number in 0..7 ~}}
|
||||
Color.{{ colors[number].name }},
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)
|
||||
{
|
||||
return new List<Color>(legacy)
|
||||
{
|
||||
{{~ for number in 8..15 ~}}
|
||||
Color.{{ colors[number].name }},
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)
|
||||
{
|
||||
return new List<Color>(standard)
|
||||
{
|
||||
{{~ for number in 16..255 ~}}
|
||||
Color.{{ colors[number].name }},
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
28
scripts/Generator/Templates/ColorTable.Generated.template
Normal file
28
scripts/Generator/Templates/ColorTable.Generated.template
Normal file
@@ -0,0 +1,28 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static Dictionary<string, int> GenerateTable()
|
||||
{
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{{~ for color in colors ~}}
|
||||
{ "{{ string.downcase color.name }}", {{ color.number }} },
|
||||
{{~ end ~}}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,3 +75,9 @@ 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
|
||||
|
||||
# RCS1057: Add empty line between declarations.
|
||||
dotnet_diagnostic.RCS1057.severity = none
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<Authors>Patrik Svensson</Authors>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl>
|
||||
<PackageIcon>small-logo.png</PackageIcon>
|
||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||
<PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
@@ -33,7 +34,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
root = false
|
||||
[*.cs]
|
||||
|
||||
# Default severity for all analyzer diagnostics
|
||||
dotnet_analyzer_diagnostic.severity = none
|
||||
|
||||
# CS1591: Missing XML comment for publicly visible type or member
|
||||
dotnet_diagnostic.CS1591.severity = none
|
||||
@@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Sample
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Use the static API to write some things to the console.
|
||||
AnsiConsole.Foreground = Color.Chartreuse2;
|
||||
AnsiConsole.Style = Styles.Underline | Styles.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("[white on red]Good[/] [red]bye[/]!");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
// We can also use System.ConsoleColor with AnsiConsole.
|
||||
foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor)))
|
||||
{
|
||||
AnsiConsole.Foreground = value;
|
||||
AnsiConsole.WriteLine("ConsoleColor.{0}", value);
|
||||
}
|
||||
|
||||
// We can get the default console via the static API.
|
||||
var console = AnsiConsole.Console;
|
||||
|
||||
// Or you can build it yourself the old fashion way.
|
||||
console = AnsiConsole.Create(
|
||||
new AnsiConsoleSettings()
|
||||
{
|
||||
Ansi = AnsiSupport.Yes,
|
||||
ColorSystem = ColorSystemSupport.Standard,
|
||||
Out = Console.Out,
|
||||
});
|
||||
|
||||
// In this case, we will find the closest colors
|
||||
// and downgrade them to the specified color system.
|
||||
console.WriteLine();
|
||||
console.Foreground = Color.Chartreuse2;
|
||||
console.Style = Styles.Underline | Styles.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.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,3 +21,6 @@ dotnet_diagnostic.CA1034.severity = none
|
||||
|
||||
# CA2000: Dispose objects before losing scope
|
||||
dotnet_diagnostic.CA2000.severity = none
|
||||
|
||||
# SA1118: Parameter should not span multiple lines
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(Styles.Bold, "\u001b[1mHello World[0m")]
|
||||
[InlineData(Styles.Dim, "\u001b[2mHello World[0m")]
|
||||
[InlineData(Styles.Italic, "\u001b[3mHello World[0m")]
|
||||
[InlineData(Styles.Underline, "\u001b[4mHello World[0m")]
|
||||
[InlineData(Styles.Invert, "\u001b[7mHello World[0m")]
|
||||
[InlineData(Styles.Conceal, "\u001b[8mHello World[0m")]
|
||||
[InlineData(Styles.SlowBlink, "\u001b[5mHello World[0m")]
|
||||
[InlineData(Styles.RapidBlink, "\u001b[6mHello World[0m")]
|
||||
[InlineData(Styles.Strikethrough, "\u001b[9mHello World[0m")]
|
||||
public void Should_Write_Style_Correctly(Styles style, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Style = style;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(Styles.Bold | Styles.Underline, "\u001b[1;4mHello World[0m")]
|
||||
[InlineData(Styles.Bold | Styles.Underline | Styles.Conceal, "\u001b[1;4;8mHello World[0m")]
|
||||
public void Should_Write_Combined_Styles_Correctly(Styles style, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Style = style;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/Spectre.Console.Tests/Extensions/StringExtensions.cs
Normal file
13
src/Spectre.Console.Tests/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string NormalizeLineEndings(this string text)
|
||||
{
|
||||
return text?.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase)
|
||||
?.Replace("\r", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,17 @@ namespace Spectre.Console.Tests
|
||||
|
||||
public string Output => _writer.ToString();
|
||||
|
||||
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes)
|
||||
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
|
||||
{
|
||||
_writer = new StringWriter();
|
||||
|
||||
Console = AnsiConsole.Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = ansi,
|
||||
ColorSystem = (ColorSystemSupport)system,
|
||||
Out = _writer,
|
||||
});
|
||||
Console = new ConsoleWithWidth(
|
||||
AnsiConsole.Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = ansi,
|
||||
ColorSystem = (ColorSystemSupport)system,
|
||||
Out = _writer,
|
||||
}), width);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
31
src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs
Normal file
31
src/Spectre.Console.Tests/Fixtures/ConsoleWithWidth.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public sealed class ConsoleWithWidth : IAnsiConsole
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
|
||||
public Capabilities Capabilities => _console.Capabilities;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height => _console.Height;
|
||||
|
||||
public Encoding Encoding => _console.Encoding;
|
||||
|
||||
public Decoration Decoration { get => _console.Decoration; set => _console.Decoration = value; }
|
||||
public Color Foreground { get => _console.Foreground; set => _console.Foreground = value; }
|
||||
public Color Background { get => _console.Background; set => _console.Background = value; }
|
||||
|
||||
public ConsoleWithWidth(IAnsiConsole console, int width)
|
||||
{
|
||||
_console = console;
|
||||
Width = width;
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
_console.Write(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/Spectre.Console.Tests/Fixtures/PlainConsole.cs
Normal file
47
src/Spectre.Console.Tests/Fixtures/PlainConsole.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public sealed class PlainConsole : IAnsiConsole, IDisposable
|
||||
{
|
||||
public Capabilities Capabilities { get; }
|
||||
public Encoding Encoding { get; }
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public Decoration Decoration { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
public Color Background { get; set; }
|
||||
|
||||
public StringWriter Writer { get; }
|
||||
public string RawOutput => Writer.ToString();
|
||||
public string Output => Writer.ToString().TrimEnd('\n');
|
||||
public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' });
|
||||
|
||||
public PlainConsole(
|
||||
int width = 80, int height = 9000, Encoding encoding = null,
|
||||
bool supportsAnsi = true, ColorSystem colorSystem = ColorSystem.Standard,
|
||||
bool legacyConsole = false)
|
||||
{
|
||||
Capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole);
|
||||
Encoding = encoding ?? Encoding.UTF8;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Writer = new StringWriter();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Writer.Dispose();
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
{
|
||||
Writer.Write(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.0.0-beta0002" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
@@ -12,7 +12,7 @@ namespace Spectre.Console.Tests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("[yellow]Hello[/]", "[93mHello[0m")]
|
||||
[InlineData("[yellow]Hello [italic]World[/]![/]", "[93mHello [0m[3;93mWorld[0m[93m![0m")]
|
||||
[InlineData("[yellow]Hello [italic]World[/]![/]", "[93mHello[0m[93m [0m[3;93mWorld[0m[93m![0m")]
|
||||
public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected)
|
||||
{
|
||||
// Given
|
||||
@@ -26,7 +26,7 @@ namespace Spectre.Console.Tests
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("[yellow]Hello [[ World[/]", "[93mHello [0m[93m[[0m[93m World[0m")]
|
||||
[InlineData("[yellow]Hello [[ World[/]", "[93mHello[0m[93m [0m[93m[[0m[93m [0m[93mWorld[0m")]
|
||||
public void Should_Be_Able_To_Escape_Tags(string markup, string expected)
|
||||
{
|
||||
// Given
|
||||
47
src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs
Normal file
47
src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Style.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(Decoration.Bold, "\u001b[1mHello World[0m")]
|
||||
[InlineData(Decoration.Dim, "\u001b[2mHello World[0m")]
|
||||
[InlineData(Decoration.Italic, "\u001b[3mHello World[0m")]
|
||||
[InlineData(Decoration.Underline, "\u001b[4mHello World[0m")]
|
||||
[InlineData(Decoration.Invert, "\u001b[7mHello World[0m")]
|
||||
[InlineData(Decoration.Conceal, "\u001b[8mHello World[0m")]
|
||||
[InlineData(Decoration.SlowBlink, "\u001b[5mHello World[0m")]
|
||||
[InlineData(Decoration.RapidBlink, "\u001b[6mHello World[0m")]
|
||||
[InlineData(Decoration.Strikethrough, "\u001b[9mHello World[0m")]
|
||||
public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Decoration = decoration;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(Decoration.Bold | Decoration.Underline, "\u001b[1;4mHello World[0m")]
|
||||
[InlineData(Decoration.Bold | Decoration.Underline | Decoration.Conceal, "\u001b[1;4;8mHello World[0m")]
|
||||
public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
|
||||
fixture.Console.Decoration = decoration;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello World");
|
||||
|
||||
// Then
|
||||
fixture.Output.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,18 @@ using System.Globalization;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Combine_Style_And_Colors()
|
||||
public void Should_Combine_Decoration_And_Colors()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.RoyalBlue1;
|
||||
fixture.Console.Background = Color.NavajoWhite1;
|
||||
fixture.Console.Style = Styles.Italic;
|
||||
fixture.Console.Decoration = Decoration.Italic;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -30,7 +30,7 @@ namespace Spectre.Console.Tests
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.Default;
|
||||
fixture.Console.Background = Color.NavajoWhite1;
|
||||
fixture.Console.Style = Styles.Italic;
|
||||
fixture.Console.Decoration = Decoration.Italic;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -46,7 +46,7 @@ namespace Spectre.Console.Tests
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.RoyalBlue1;
|
||||
fixture.Console.Background = Color.Default;
|
||||
fixture.Console.Style = Styles.Italic;
|
||||
fixture.Console.Decoration = Decoration.Italic;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -56,13 +56,13 @@ namespace Spectre.Console.Tests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Include_Style_If_Set_To_None()
|
||||
public void Should_Not_Include_Decoration_If_Set_To_None()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
|
||||
fixture.Console.Foreground = Color.RoyalBlue1;
|
||||
fixture.Console.Background = Color.NavajoWhite1;
|
||||
fixture.Console.Style = Styles.None;
|
||||
fixture.Console.Decoration = Decoration.None;
|
||||
|
||||
// When
|
||||
fixture.Console.Write("Hello");
|
||||
@@ -246,6 +246,38 @@ namespace Spectre.Console.Tests
|
||||
|
||||
public sealed class WriteLine
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Reset_Colors_Correctly_After_Line_Break()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
fixture.Console.Background = ConsoleColor.Red;
|
||||
fixture.Console.WriteLine("Hello");
|
||||
fixture.Console.Background = ConsoleColor.Green;
|
||||
fixture.Console.WriteLine("World");
|
||||
|
||||
// Then
|
||||
fixture.Output.NormalizeLineEndings()
|
||||
.ShouldBe("[101mHello[0m\n[102mWorld[0m\n");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text()
|
||||
{
|
||||
// Given
|
||||
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
fixture.Console.Background = ConsoleColor.Red;
|
||||
fixture.Console.WriteLine("Hello\nWorld");
|
||||
|
||||
// Then
|
||||
fixture.Output.NormalizeLineEndings()
|
||||
.ShouldBe("[101mHello[0m\n[101mWorld[0m\n");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(AnsiSupport.Yes)]
|
||||
[InlineData(AnsiSupport.No)]
|
||||
42
src/Spectre.Console.Tests/Unit/BorderTests.cs
Normal file
42
src/Spectre.Console.Tests/Unit/BorderTests.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Composition;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class BorderTests
|
||||
{
|
||||
public sealed class TheGetBorderMethod
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(BorderKind.None, false, typeof(NoBorder))]
|
||||
[InlineData(BorderKind.Ascii, false, typeof(AsciiBorder))]
|
||||
[InlineData(BorderKind.Square, false, typeof(SquareBorder))]
|
||||
[InlineData(BorderKind.Rounded, false, typeof(RoundedBorder))]
|
||||
[InlineData(BorderKind.None, true, typeof(NoBorder))]
|
||||
[InlineData(BorderKind.Ascii, true, typeof(AsciiBorder))]
|
||||
[InlineData(BorderKind.Square, true, typeof(SquareBorder))]
|
||||
[InlineData(BorderKind.Rounded, true, typeof(SquareBorder))]
|
||||
public void Should_Return_Correct_Border_For_Specified_Kind(BorderKind kind, bool safe, Type expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = Border.GetBorder(kind, safe);
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Unknown_Border_Kind_Is_Specified()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue, false));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Unknown border kind");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
254
src/Spectre.Console.Tests/Unit/ColorTests.cs
Normal file
254
src/Spectre.Console.Tests/Unit/ColorTests.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class ColorTests
|
||||
{
|
||||
public sealed class TheEqualsMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Consider_Color_And_Non_Color_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
|
||||
// When
|
||||
var result = color1.Equals("Foo");
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Consider_Same_Colors_Equal_By_Component()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 0, 128);
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Consider_Same_Known_Colors_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = Color.Cyan1;
|
||||
var color2 = Color.Cyan1;
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Consider_Known_Color_And_Color_With_Same_Components_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = Color.Cyan1;
|
||||
var color2 = new Color(0, 255, 255);
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Consider_Different_Colors_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 128, 128);
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Shourd_Not_Consider_Black_And_Default_Colors_Equal()
|
||||
{
|
||||
// Given
|
||||
var color1 = Color.Default;
|
||||
var color2 = Color.Black;
|
||||
|
||||
// When
|
||||
var result = color1.Equals(color2);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheGetHashCodeMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Same_HashCode_For_Same_Colors()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 0, 128);
|
||||
|
||||
// When
|
||||
var hash1 = color1.GetHashCode();
|
||||
var hash2 = color2.GetHashCode();
|
||||
|
||||
// Then
|
||||
hash1.ShouldBe(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Different_HashCode_For_Different_Colors()
|
||||
{
|
||||
// Given
|
||||
var color1 = new Color(128, 0, 128);
|
||||
var color2 = new Color(128, 128, 128);
|
||||
|
||||
// When
|
||||
var hash1 = color1.GetHashCode();
|
||||
var hash2 = color2.GetHashCode();
|
||||
|
||||
// Then
|
||||
hash1.ShouldNotBe(hash2);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ImplicitConversions
|
||||
{
|
||||
public sealed class Int32ToColor
|
||||
{
|
||||
public static IEnumerable<object[]> Data =>
|
||||
Enumerable.Range(0, 255)
|
||||
.Select(number => new object[] { number });
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Data))]
|
||||
public void Should_Return_Expected_Color(int number)
|
||||
{
|
||||
// Given, When
|
||||
var result = (Color)number;
|
||||
|
||||
// Then
|
||||
result.ShouldBe(Color.FromInt32(number));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Integer_Is_Lower_Than_Zero()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => (Color)(-1));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Color number must be between 0 and 255");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Integer_Is_Higher_Than_255()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => (Color)256);
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("Color number must be between 0 and 255");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConsoleColorToColor
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ConsoleColor.Black, 0)]
|
||||
[InlineData(ConsoleColor.DarkRed, 1)]
|
||||
[InlineData(ConsoleColor.DarkGreen, 2)]
|
||||
[InlineData(ConsoleColor.DarkYellow, 3)]
|
||||
[InlineData(ConsoleColor.DarkBlue, 4)]
|
||||
[InlineData(ConsoleColor.DarkMagenta, 5)]
|
||||
[InlineData(ConsoleColor.DarkCyan, 6)]
|
||||
[InlineData(ConsoleColor.Gray, 7)]
|
||||
[InlineData(ConsoleColor.DarkGray, 8)]
|
||||
[InlineData(ConsoleColor.Red, 9)]
|
||||
[InlineData(ConsoleColor.Green, 10)]
|
||||
[InlineData(ConsoleColor.Yellow, 11)]
|
||||
[InlineData(ConsoleColor.Blue, 12)]
|
||||
[InlineData(ConsoleColor.Magenta, 13)]
|
||||
[InlineData(ConsoleColor.Cyan, 14)]
|
||||
[InlineData(ConsoleColor.White, 15)]
|
||||
public void Should_Return_Expected_Color(ConsoleColor color, int expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = (Color)color;
|
||||
|
||||
// Then
|
||||
result.ShouldBe(Color.FromInt32(expected));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ColorToConsoleColor
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, ConsoleColor.Black)]
|
||||
[InlineData(1, ConsoleColor.DarkRed)]
|
||||
[InlineData(2, ConsoleColor.DarkGreen)]
|
||||
[InlineData(3, ConsoleColor.DarkYellow)]
|
||||
[InlineData(4, ConsoleColor.DarkBlue)]
|
||||
[InlineData(5, ConsoleColor.DarkMagenta)]
|
||||
[InlineData(6, ConsoleColor.DarkCyan)]
|
||||
[InlineData(7, ConsoleColor.Gray)]
|
||||
[InlineData(8, ConsoleColor.DarkGray)]
|
||||
[InlineData(9, ConsoleColor.Red)]
|
||||
[InlineData(10, ConsoleColor.Green)]
|
||||
[InlineData(11, ConsoleColor.Yellow)]
|
||||
[InlineData(12, ConsoleColor.Blue)]
|
||||
[InlineData(13, ConsoleColor.Magenta)]
|
||||
[InlineData(14, ConsoleColor.Cyan)]
|
||||
[InlineData(15, ConsoleColor.White)]
|
||||
public void Should_Return_Expected_ConsoleColor_For_Known_Color(int color, ConsoleColor expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = (ConsoleColor)Color.FromInt32(color);
|
||||
|
||||
// Then
|
||||
result.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheToStringMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Color_Name_For_Known_Colors()
|
||||
{
|
||||
// Given, When
|
||||
var name = Color.Fuchsia.ToString();
|
||||
|
||||
// Then
|
||||
name.ShouldBe("fuchsia");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Hex_String_For_Unknown_Colors()
|
||||
{
|
||||
// Given, When
|
||||
var name = new Color(128, 0, 128).ToString();
|
||||
|
||||
// Then
|
||||
name.ShouldBe("#800080 (RGB=128,0,128)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
210
src/Spectre.Console.Tests/Unit/GridTests.cs
Normal file
210
src/Spectre.Console.Tests/Unit/GridTests.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class GridTests
|
||||
{
|
||||
public sealed class TheAddColumnMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Rows_Are_Not_Empty()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Hello World!");
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddColumn());
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>()
|
||||
.Message.ShouldBe("Cannot add new columns to grid with existing rows.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Rows_Are_Null()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow(null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("columns");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow("Foo"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are less than the number of grid columns.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddRow("Foo", "Bar"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are greater than the number of grid columns.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddEmptyRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Add_Empty_Row()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumns(2);
|
||||
grid.AddRow("Foo", "Bar");
|
||||
grid.AddEmptyRow();
|
||||
grid.AddRow("Qux", "Corgi");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("Foo Bar ");
|
||||
console.Lines[1].ShouldBe(" ");
|
||||
console.Lines[2].ShouldBe("Qux Corgi");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
grid.AddColumn();
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(2);
|
||||
console.Lines[0].ShouldBe("Qux Corgi Waldo");
|
||||
console.Lines[1].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid_Column_Alignment_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Alignment = Justify.Right });
|
||||
grid.AddColumn(new GridColumn { Alignment = Justify.Center });
|
||||
grid.AddColumn(new GridColumn { Alignment = Justify.Left });
|
||||
grid.AddRow("Foo", "Bar", "Baz");
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe(" Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe(" Qux Corgi Waldo");
|
||||
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Use_Default_Padding()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumns(3);
|
||||
grid.AddRow("Foo", "Bar", "Baz");
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe("Qux Corgi Waldo");
|
||||
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Explicit_Grid_Column_Padding_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(3, 0) });
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(0, 3) });
|
||||
grid.AddRow("Foo", "Bar", "Baz");
|
||||
grid.AddRow("Qux", "Corgi", "Waldo");
|
||||
grid.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe(" Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe(" Qux Corgi Waldo ");
|
||||
console.Lines[2].ShouldBe(" GraultGarplyFred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Grid()
|
||||
{
|
||||
var console = new PlainConsole(width: 80);
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) });
|
||||
grid.AddRow("[bold]Options[/]", string.Empty);
|
||||
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
|
||||
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");
|
||||
|
||||
// When
|
||||
console.Render(grid);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(4);
|
||||
console.Lines[0].ShouldBe("Options ");
|
||||
console.Lines[1].ShouldBe(" -h, --help Show command line help. ");
|
||||
console.Lines[2].ShouldBe(" -c, --configuration The configuration to run for. ");
|
||||
console.Lines[3].ShouldBe(" The default for most projects is Debug.");
|
||||
}
|
||||
}
|
||||
}
|
||||
180
src/Spectre.Console.Tests/Unit/PanelTests.cs
Normal file
180
src/Spectre.Console.Tests/Unit/PanelTests.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class PanelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Panel()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(new Text("Hello World")));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌─────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└─────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_With_Padding()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(new Text("Hello World"))
|
||||
{
|
||||
Padding = new Padding(3, 5),
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌───────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└───────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_With_Unicode_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(new Text(" \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(new Text("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.Markup("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"));
|
||||
|
||||
// When
|
||||
console.Render(text);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ I heard you like 📦 │");
|
||||
console.Lines[2].ShouldBe("│ │");
|
||||
console.Lines[3].ShouldBe("│ │");
|
||||
console.Lines[4].ShouldBe("│ │");
|
||||
console.Lines[5].ShouldBe("│ So I put a 📦 in a 📦 │");
|
||||
console.Lines[6].ShouldBe("└───────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Expand_Panel_If_Enabled()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(new Text("Hello World"))
|
||||
{
|
||||
Expand = true,
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].Length.ShouldBe(80);
|
||||
console.Lines[0].ShouldBe("┌──────────────────────────────────────────────────────────────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Justify_Child_To_Right()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 25);
|
||||
|
||||
// When
|
||||
console.Render(
|
||||
new Panel(
|
||||
new Text("Hello World").WithAlignment(Justify.Right))
|
||||
{
|
||||
Expand = true,
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└───────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Justify_Child_To_Center()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 25);
|
||||
|
||||
// When
|
||||
console.Render(
|
||||
new Panel(
|
||||
new Text("Hello World").WithAlignment(Justify.Center))
|
||||
{
|
||||
Expand = true,
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Hello World │");
|
||||
console.Lines[2].ShouldBe("└───────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Panel_Inside_Panel_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
|
||||
// When
|
||||
console.Render(new Panel(new Panel(new Text("Hello World"))));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(5);
|
||||
console.Lines[0].ShouldBe("┌─────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ ┌─────────────┐ │");
|
||||
console.Lines[2].ShouldBe("│ │ Hello World │ │");
|
||||
console.Lines[3].ShouldBe("│ └─────────────┘ │");
|
||||
console.Lines[4].ShouldBe("└─────────────────┘");
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/Spectre.Console.Tests/Unit/SegmentTests.cs
Normal file
92
src/Spectre.Console.Tests/Unit/SegmentTests.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Shouldly;
|
||||
using Spectre.Console.Composition;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class SegmentTests
|
||||
{
|
||||
public sealed class TheSplitMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Split_Segment_Correctly()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
|
||||
var segment = new Segment("Foo Bar", style);
|
||||
|
||||
// When
|
||||
var (first, second) = segment.Split(3);
|
||||
|
||||
// Then
|
||||
first.Text.ShouldBe("Foo");
|
||||
first.Style.ShouldBe(style);
|
||||
second.Text.ShouldBe(" Bar");
|
||||
second.Style.ShouldBe(style);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheSplitLinesMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Split_Segment()
|
||||
{
|
||||
var lines = Segment.SplitLines(
|
||||
new[]
|
||||
{
|
||||
new Segment("Foo"),
|
||||
new Segment("Bar"),
|
||||
new Segment("\n"),
|
||||
new Segment("Baz"),
|
||||
new Segment("Qux"),
|
||||
new Segment("\n"),
|
||||
new Segment("Corgi"),
|
||||
});
|
||||
|
||||
// Then
|
||||
lines.Count.ShouldBe(3);
|
||||
|
||||
lines[0].Count.ShouldBe(2);
|
||||
lines[0][0].Text.ShouldBe("Foo");
|
||||
lines[0][1].Text.ShouldBe("Bar");
|
||||
|
||||
lines[1].Count.ShouldBe(2);
|
||||
lines[1][0].Text.ShouldBe("Baz");
|
||||
lines[1][1].Text.ShouldBe("Qux");
|
||||
|
||||
lines[2].Count.ShouldBe(1);
|
||||
lines[2][0].Text.ShouldBe("Corgi");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Split_Segments_With_Linebreak_In_Text()
|
||||
{
|
||||
var lines = Segment.SplitLines(
|
||||
new[]
|
||||
{
|
||||
new Segment("Foo\n"),
|
||||
new Segment("Bar\n"),
|
||||
new Segment("Baz"),
|
||||
new Segment("Qux\n"),
|
||||
new Segment("Corgi"),
|
||||
});
|
||||
|
||||
// Then
|
||||
lines.Count.ShouldBe(4);
|
||||
|
||||
lines[0].Count.ShouldBe(1);
|
||||
lines[0][0].Text.ShouldBe("Foo");
|
||||
|
||||
lines[1].Count.ShouldBe(1);
|
||||
lines[1][0].Text.ShouldBe("Bar");
|
||||
|
||||
lines[2].Count.ShouldBe(2);
|
||||
lines[2][0].Text.ShouldBe("Baz");
|
||||
lines[2][1].Text.ShouldBe("Qux");
|
||||
|
||||
lines[3].Count.ShouldBe(1);
|
||||
lines[3][0].Text.ShouldBe("Corgi");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
216
src/Spectre.Console.Tests/Unit/StyleTests.cs
Normal file
216
src/Spectre.Console.Tests/Unit/StyleTests.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
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("b", Decoration.Bold)]
|
||||
[InlineData("dim", Decoration.Dim)]
|
||||
[InlineData("i", Decoration.Italic)]
|
||||
[InlineData("italic", Decoration.Italic)]
|
||||
[InlineData("underline", Decoration.Underline)]
|
||||
[InlineData("u", Decoration.Underline)]
|
||||
[InlineData("invert", Decoration.Invert)]
|
||||
[InlineData("conceal", Decoration.Conceal)]
|
||||
[InlineData("slowblink", Decoration.SlowBlink)]
|
||||
[InlineData("rapidblink", Decoration.RapidBlink)]
|
||||
[InlineData("strikethrough", Decoration.Strikethrough)]
|
||||
[InlineData("s", 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'.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("#FF0000 on #0000FF")]
|
||||
[InlineData("#F00 on #00F")]
|
||||
public void Should_Parse_Hex_Colors_Correctly(string style)
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.Parse(style);
|
||||
|
||||
// Then
|
||||
result.Foreground.ShouldBe(Color.Red);
|
||||
result.Background.ShouldBe(Color.Blue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("#", "Invalid hex color '#'.")]
|
||||
[InlineData("#FF00FF00FF", "Invalid hex color '#FF00FF00FF'.")]
|
||||
[InlineData("#FOO", "Invalid hex color '#FOO'. Could not find any recognizable digits.")]
|
||||
public void Should_Return_Error_If_Hex_Color_Is_Invalid(string style, string expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Style.Parse(style));
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeNull();
|
||||
result.Message.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("rgb(255,0,0) on rgb(0,0,255)")]
|
||||
public void Should_Parse_Rgb_Colors_Correctly(string style)
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.Parse(style);
|
||||
|
||||
// Then
|
||||
result.Foreground.ShouldBe(Color.Red);
|
||||
result.Background.ShouldBe(Color.Blue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("rgb()", "Invalid RGB color 'rgb()'.")]
|
||||
[InlineData("rgb(", "Invalid RGB color 'rgb('.")]
|
||||
[InlineData("rgb(255)", "Invalid RGB color 'rgb(255)'.")]
|
||||
[InlineData("rgb(255,255)", "Invalid RGB color 'rgb(255,255)'.")]
|
||||
[InlineData("rgb(255,255,255", "Invalid RGB color 'rgb(255,255,255'.")]
|
||||
[InlineData("rgb(A,B,C)", "Invalid RGB color 'rgb(A,B,C)'. Input string was not in a correct format.")]
|
||||
public void Should_Return_Error_If_Rgb_Color_Is_Invalid(string style, string expected)
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => Style.Parse(style));
|
||||
|
||||
// Then
|
||||
result.ShouldNotBeNull();
|
||||
result.Message.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheTryParseMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_True_If_Parsing_Succeeded()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("bold", out var style);
|
||||
|
||||
// Then
|
||||
result.ShouldBeTrue();
|
||||
style.ShouldNotBeNull();
|
||||
style.Decoration.ShouldBe(Decoration.Bold);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_False_If_Parsing_Failed()
|
||||
{
|
||||
// Given, When
|
||||
var result = Style.TryParse("lol", out _);
|
||||
|
||||
// Then
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
374
src/Spectre.Console.Tests/Unit/TableTests.cs
Normal file
374
src/Spectre.Console.Tests/Unit/TableTests.cs
Normal file
@@ -0,0 +1,374 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class TableTests
|
||||
{
|
||||
public sealed class TheAddColumnMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Column_Is_Null()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddColumn((string)null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("column");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Rows_Are_Not_Empty()
|
||||
{
|
||||
// Given
|
||||
var grid = new Table();
|
||||
grid.AddColumn("Foo");
|
||||
grid.AddRow("Hello World");
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => grid.AddColumn("Bar"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>()
|
||||
.Message.ShouldBe("Cannot add new columns to table with existing rows.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddColumnsMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Columns_Are_Null()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddColumns((string[])null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("columns");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Rows_Are_Null()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddRow(null));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<ArgumentNullException>()
|
||||
.ParamName.ShouldBe("columns");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
table.AddColumn("Hello");
|
||||
table.AddColumn("World");
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddRow("Foo"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are less than the number of table columns.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns()
|
||||
{
|
||||
// Given
|
||||
var table = new Table();
|
||||
table.AddColumn("Hello");
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => table.AddRow("Foo", "Bar"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<InvalidOperationException>();
|
||||
result.Message.ShouldBe("The number of row columns are greater than the number of table columns.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheAddEmptyRowMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Table_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddEmptyRow();
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ │ │ │");
|
||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("└────────┴────────┴───────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Nested_In_Panels_Correctly()
|
||||
{
|
||||
// A simple table
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table() { Border = BorderKind.Rounded };
|
||||
table.AddColumn("Foo");
|
||||
table.AddColumn("Bar");
|
||||
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right });
|
||||
table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// Render a table in some panels.
|
||||
console.Render(new Panel(new Panel(table)
|
||||
{
|
||||
Border = BorderKind.Ascii,
|
||||
}));
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(11);
|
||||
console.Lines[00].ShouldBe("┌───────────────────────────────────┐");
|
||||
console.Lines[01].ShouldBe("│ +-------------------------------+ │");
|
||||
console.Lines[02].ShouldBe("│ | ╭──────────┬────────┬───────╮ | │");
|
||||
console.Lines[03].ShouldBe("│ | │ Foo │ Bar │ Baz │ | │");
|
||||
console.Lines[04].ShouldBe("│ | ├──────────┼────────┼───────┤ | │");
|
||||
console.Lines[05].ShouldBe("│ | │ Qux │ Corgi │ Waldo │ | │");
|
||||
console.Lines[06].ShouldBe("│ | │ Quuuuuux │ │ │ | │");
|
||||
console.Lines[07].ShouldBe("│ | │ Grault │ Garply │ Fred │ | │");
|
||||
console.Lines[08].ShouldBe("│ | ╰──────────┴────────┴───────╯ | │");
|
||||
console.Lines[09].ShouldBe("│ +-------------------------------+ │");
|
||||
console.Lines[10].ShouldBe("└───────────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Column_Justification_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumn(new TableColumn("Foo") { Alignment = Justify.Left });
|
||||
table.AddColumn(new TableColumn("Bar") { Alignment = Justify.Right });
|
||||
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Center });
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Lorem ipsum dolor sit amet");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬────────────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼────────────────────────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Lorem ipsum dolor sit amet │");
|
||||
console.Lines[5].ShouldBe("└────────┴────────┴────────────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Expand_Table_To_Available_Space_If_Specified()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table() { Expand = true };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].Length.ShouldBe(80);
|
||||
console.Lines[0].ShouldBe("┌───────────────────────────┬───────────────────────────┬──────────────────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├───────────────────────────┼───────────────────────────┼──────────────────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Ascii_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.Ascii };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("+-------------------------+");
|
||||
console.Lines[1].ShouldBe("| Foo | Bar | Baz |");
|
||||
console.Lines[2].ShouldBe("|--------+--------+-------|");
|
||||
console.Lines[3].ShouldBe("| Qux | Corgi | Waldo |");
|
||||
console.Lines[4].ShouldBe("| Grault | Garply | Fred |");
|
||||
console.Lines[5].ShouldBe("+-------------------------+");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Rounded_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.Rounded };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(6);
|
||||
console.Lines[0].ShouldBe("╭────────┬────────┬───────╮");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[5].ShouldBe("╰────────┴────────┴───────╯");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_No_Border_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table { Border = BorderKind.None };
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("Foo Bar Baz ");
|
||||
console.Lines[1].ShouldBe("Qux Corgi Waldo");
|
||||
console.Lines[2].ShouldBe("Grault Garply Fred ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar", "Baz");
|
||||
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬───────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼───────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Quuux │ │ │");
|
||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[6].ShouldBe("└────────┴────────┴───────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_With_Cell_Padding_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar");
|
||||
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) });
|
||||
table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
|
||||
table.AddRow("Grault", "Garply", "Fred");
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(7);
|
||||
console.Lines[0].ShouldBe("┌────────┬────────┬──────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("├────────┼────────┼──────────┤");
|
||||
console.Lines[3].ShouldBe("│ Qux │ Corgi │ Waldo │");
|
||||
console.Lines[4].ShouldBe("│ Quuux │ │ │");
|
||||
console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred │");
|
||||
console.Lines[6].ShouldBe("└────────┴────────┴──────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Render_Table_Without_Footer_If_No_Rows_Are_Added()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 80);
|
||||
var table = new Table();
|
||||
table.AddColumns("Foo", "Bar");
|
||||
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) });
|
||||
|
||||
// When
|
||||
console.Render(table);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(3);
|
||||
console.Lines[0].ShouldBe("┌─────┬─────┬────────┐");
|
||||
console.Lines[1].ShouldBe("│ Foo │ Bar │ Baz │");
|
||||
console.Lines[2].ShouldBe("└─────┴─────┴────────┘");
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/Spectre.Console.Tests/Unit/TextTests.cs
Normal file
85
src/Spectre.Console.Tests/Unit/TextTests.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Text;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Composition;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class TextTests
|
||||
{
|
||||
public sealed class Measuring
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_The_Longest_Word_As_Minimum_Width()
|
||||
{
|
||||
var text = new Text("Foo Bar Baz\nQux\nLol mobile");
|
||||
|
||||
var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80);
|
||||
|
||||
result.Min.ShouldBe(6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_The_Longest_Line_As_Maximum_Width()
|
||||
{
|
||||
var text = new Text("Foo Bar Baz\nQux\nLol mobile");
|
||||
|
||||
var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80);
|
||||
|
||||
result.Max.ShouldBe(11);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Rendering
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Unstyled_Text_As_Expected()
|
||||
{
|
||||
// Given
|
||||
var fixture = new PlainConsole(width: 80);
|
||||
var text = new Text("Hello World");
|
||||
|
||||
// When
|
||||
fixture.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe("Hello World");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Line_Breaks()
|
||||
{
|
||||
// Given
|
||||
var fixture = new PlainConsole(width: 5);
|
||||
var text = new Text("Hello\n\nWorld");
|
||||
|
||||
// When
|
||||
fixture.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.RawOutput.ShouldBe("Hello\n\nWorld");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(5, "Hello World", "Hello\nWorld")]
|
||||
[InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")]
|
||||
public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width(
|
||||
int width, string input, string expected)
|
||||
{
|
||||
// Given
|
||||
var fixture = new PlainConsole(width);
|
||||
var text = new Text(input);
|
||||
|
||||
// When
|
||||
fixture.Render(text);
|
||||
|
||||
// Then
|
||||
fixture.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "Spectre.
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{272E6092-BD31-4EB6-A9FF-F4179F91958F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20595AD4-8D75-4AF8-B6BC-9C38C160423F}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
@@ -17,6 +15,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
stylecop.json = stylecop.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F0575243-121F-4DEE-9F6B-246E26DC0844}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Table", "..\examples\Table\Table.csproj", "{94ECCBA8-7EBF-4B53-8379-52EB2327417E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panel", "..\examples\Panel\Panel.csproj", "{BFF37228-B376-4ADD-9657-4E501F929713}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grid", "..\examples\Grid\Grid.csproj", "{C7FF6FDB-FB59-4517-8669-521C96AB7323}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Colors", "..\examples\Colors\Colors.csproj", "{1F51C55C-BA4C-4856-9001-0F7924FFB179}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -51,22 +59,64 @@ Global
|
||||
{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x86.Build.0 = Release|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x64.Build.0 = Release|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x64.Build.0 = Release|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{94ECCBA8-7EBF-4B53-8379-52EB2327417E} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||
{BFF37228-B376-4ADD-9657-4E501F929713} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||
{C7FF6FDB-FB59-4517-8669-521C96AB7323} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||
{1F51C55C-BA4C-4856-9001-0F7924FFB179} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||
EndGlobalSection
|
||||
|
||||
19
src/Spectre.Console/AnsiConsole.Rendering.cs
Normal file
19
src/Spectre.Console/AnsiConsole.Rendering.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Spectre.Console.Composition;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// A console capable of writing ANSI escape sequences.
|
||||
/// </summary>
|
||||
public static partial class AnsiConsole
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the specified object to the console.
|
||||
/// </summary>
|
||||
/// <param name="renderable">The object to render.</param>
|
||||
public static void Render(IRenderable renderable)
|
||||
{
|
||||
Console.Render(renderable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ namespace Spectre.Console
|
||||
{
|
||||
private static readonly Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(() =>
|
||||
{
|
||||
return Create(new AnsiConsoleSettings
|
||||
var console = Create(new AnsiConsoleSettings
|
||||
{
|
||||
Ansi = AnsiSupport.Detect,
|
||||
ColorSystem = ColorSystemSupport.Detect,
|
||||
Out = System.Console.Out,
|
||||
});
|
||||
Created = true;
|
||||
return console;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +28,9 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets the console's capabilities.
|
||||
/// </summary>
|
||||
public static AnsiConsoleCapabilities Capabilities => Console.Capabilities;
|
||||
public static Capabilities Capabilities => Console.Capabilities;
|
||||
|
||||
internal static bool Created { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer width of the console.
|
||||
@@ -63,12 +67,12 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style.
|
||||
/// Gets or sets the text decoration.
|
||||
/// </summary>
|
||||
public static Styles Style
|
||||
public static Decoration Decoration
|
||||
{
|
||||
get => Console.Style;
|
||||
set => Console.Style = value;
|
||||
get => Console.Decoration;
|
||||
set => Console.Decoration = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,7 +87,7 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets colors and styles to the default ones.
|
||||
/// Resets colors and text decorations.
|
||||
/// </summary>
|
||||
public static void Reset()
|
||||
{
|
||||
@@ -91,15 +95,15 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current style back to the default one.
|
||||
/// Resets the current applied text decorations.
|
||||
/// </summary>
|
||||
public static void ResetStyle()
|
||||
public static void ResetDecoration()
|
||||
{
|
||||
Console.ResetStyle();
|
||||
Console.ResetDecoration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the foreground and background colors to the default ones.
|
||||
/// Resets the current applied foreground and background colors.
|
||||
/// </summary>
|
||||
public static void ResetColors()
|
||||
{
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents console capabilities.
|
||||
/// </summary>
|
||||
public sealed class AnsiConsoleCapabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
/// the console supports Ansi.
|
||||
/// </summary>
|
||||
public bool SupportsAnsi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color system.
|
||||
/// </summary>
|
||||
public ColorSystem ColorSystem { get; }
|
||||
|
||||
internal AnsiConsoleCapabilities(bool supportsAnsi, ColorSystem colorSystem)
|
||||
{
|
||||
SupportsAnsi = supportsAnsi;
|
||||
ColorSystem = colorSystem;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
|
||||
var bits = ColorSystem switch
|
||||
{
|
||||
ColorSystem.NoColors => "1 bit",
|
||||
ColorSystem.Legacy => "3 bits",
|
||||
ColorSystem.Standard => "4 bits",
|
||||
ColorSystem.EightBit => "8 bits",
|
||||
ColorSystem.TrueColor => "24 bits",
|
||||
_ => "?"
|
||||
};
|
||||
|
||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem} ({bits})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,6 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets or sets the out buffer.
|
||||
/// </summary>
|
||||
public TextWriter Out { get; set; }
|
||||
public TextWriter? Out { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
59
src/Spectre.Console/Capabilities.cs
Normal file
59
src/Spectre.Console/Capabilities.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents console capabilities.
|
||||
/// </summary>
|
||||
public sealed class Capabilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
/// the console supports Ansi.
|
||||
/// </summary>
|
||||
public bool SupportsAnsi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color system.
|
||||
/// </summary>
|
||||
public ColorSystem ColorSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not
|
||||
/// this is a legacy console (cmd.exe).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only relevant when running on Microsoft Windows.
|
||||
/// </remarks>
|
||||
public bool LegacyConsole { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Capabilities"/> class.
|
||||
/// </summary>
|
||||
/// <param name="supportsAnsi">Whether or not ANSI escape sequences are supported.</param>
|
||||
/// <param name="colorSystem">The color system that is supported.</param>
|
||||
/// <param name="legacyConsole">Whether or not this is a legacy console.</param>
|
||||
public Capabilities(bool supportsAnsi, ColorSystem colorSystem, bool legacyConsole)
|
||||
{
|
||||
SupportsAnsi = supportsAnsi;
|
||||
ColorSystem = colorSystem;
|
||||
LegacyConsole = legacyConsole;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
var supportsAnsi = SupportsAnsi ? "Yes" : "No";
|
||||
var legacyConsole = LegacyConsole ? "Legacy" : "Modern";
|
||||
var bits = ColorSystem switch
|
||||
{
|
||||
ColorSystem.NoColors => "1 bit",
|
||||
ColorSystem.Legacy => "3 bits",
|
||||
ColorSystem.Standard => "4 bits",
|
||||
ColorSystem.EightBit => "8 bits",
|
||||
ColorSystem.TrueColor => "24 bits",
|
||||
_ => "?",
|
||||
};
|
||||
|
||||
return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})";
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
@@ -17,7 +17,7 @@ namespace Spectre.Console
|
||||
|
||||
static Color()
|
||||
{
|
||||
Default = new Color(0, "default", 0, 0, 0, true);
|
||||
Default = new Color(0, 0, 0, 0, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,11 +35,6 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public byte B { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the color, if any.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the color, if any.
|
||||
/// </summary>
|
||||
@@ -62,7 +57,6 @@ namespace Spectre.Console
|
||||
G = green;
|
||||
B = blue;
|
||||
IsDefault = false;
|
||||
Name = null;
|
||||
Number = null;
|
||||
}
|
||||
|
||||
@@ -80,7 +74,7 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Color color && Equals(color);
|
||||
}
|
||||
@@ -88,7 +82,8 @@ namespace Spectre.Console
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Color other)
|
||||
{
|
||||
return Number == other.Number || (R == other.R && G == other.G && B == other.B);
|
||||
return (IsDefault && other.IsDefault) ||
|
||||
(IsDefault == other.IsDefault && R == other.R && G == other.G && B == other.B);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -113,6 +108,15 @@ namespace Spectre.Console
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a <see cref="int"/> to a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="number">The color number to convert.</param>
|
||||
public static implicit operator Color(int number)
|
||||
{
|
||||
return FromInt32(number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
@@ -123,18 +127,12 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a color number into a <see cref="Color"/>.
|
||||
/// Convers a <see cref="Color"/> to a <see cref="ConsoleColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="number">The color number.</param>
|
||||
/// <returns>The color representing the specified color number.</returns>
|
||||
public static Color FromColorNumber(int number)
|
||||
/// <param name="color">The console color to convert.</param>
|
||||
public static implicit operator ConsoleColor(Color color)
|
||||
{
|
||||
if (number < 0 || number > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Color number must be between 0 and 255");
|
||||
}
|
||||
|
||||
return ColorPalette.EightBit.First(x => x.Number == number);
|
||||
return ToConsoleColor(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -179,6 +177,16 @@ namespace Spectre.Console
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a color number into a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="number">The color number.</param>
|
||||
/// <returns>The color representing the specified color number.</returns>
|
||||
public static Color FromInt32(int number)
|
||||
{
|
||||
return ColorTable.GetColor(number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convers a <see cref="ConsoleColor"/> to a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
@@ -207,5 +215,20 @@ namespace Spectre.Console
|
||||
_ => Default,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (Number != null)
|
||||
{
|
||||
var name = ColorTable.GetName(Number.Value);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2} (RGB={0},{1},{2})", R, G, B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
src/Spectre.Console/Composition/Border.cs
Normal file
108
src/Spectre.Console/Composition/Border.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a border used by tables.
|
||||
/// </summary>
|
||||
public abstract class Border
|
||||
{
|
||||
private readonly Dictionary<BorderPart, string> _lookup;
|
||||
|
||||
private static readonly Dictionary<BorderKind, Border> _borders = new Dictionary<BorderKind, Border>
|
||||
{
|
||||
{ BorderKind.None, new NoBorder() },
|
||||
{ BorderKind.Ascii, new AsciiBorder() },
|
||||
{ BorderKind.Square, new SquareBorder() },
|
||||
{ BorderKind.Rounded, new RoundedBorder() },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<BorderKind, BorderKind> _safeLookup = new Dictionary<BorderKind, BorderKind>
|
||||
{
|
||||
{ BorderKind.Rounded, BorderKind.Square },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Border"/> class.
|
||||
/// </summary>
|
||||
protected Border()
|
||||
{
|
||||
_lookup = Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>.
|
||||
/// </summary>
|
||||
/// <param name="kind">The kind of border to get.</param>
|
||||
/// <param name="safe">Whether or not to get a "safe" border that can be rendered in a legacy console.</param>
|
||||
/// <returns>A <see cref="Border"/> instance representing the specified <see cref="BorderKind"/>.</returns>
|
||||
public static Border GetBorder(BorderKind kind, bool safe)
|
||||
{
|
||||
if (safe && _safeLookup.TryGetValue(kind, out var safeKind))
|
||||
{
|
||||
kind = safeKind;
|
||||
}
|
||||
|
||||
if (!_borders.TryGetValue(kind, out var border))
|
||||
{
|
||||
throw new InvalidOperationException("Unknown border kind");
|
||||
}
|
||||
|
||||
return border;
|
||||
}
|
||||
|
||||
private Dictionary<BorderPart, string> Initialize()
|
||||
{
|
||||
var lookup = new Dictionary<BorderPart, string>();
|
||||
foreach (BorderPart? part in Enum.GetValues(typeof(BorderPart)))
|
||||
{
|
||||
if (part == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = GetBoxPart(part.Value);
|
||||
if (text.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
||||
}
|
||||
|
||||
lookup.Add(part.Value, GetBoxPart(part.Value));
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <param name="count">The number of repetitions.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(BorderPart part, int count)
|
||||
{
|
||||
// TODO: This need some optimization...
|
||||
return string.Join(string.Empty, Enumerable.Repeat(GetBoxPart(part)[0], count));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(BorderPart part)
|
||||
{
|
||||
return _lookup[part].ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character representing the specified border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get the character representation for.</param>
|
||||
/// <returns>A character representation of the specified border part.</returns>
|
||||
protected abstract string GetBoxPart(BorderPart part);
|
||||
}
|
||||
}
|
||||
28
src/Spectre.Console/Composition/BorderKind.cs
Normal file
28
src/Spectre.Console/Composition/BorderKind.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents different kinds of borders.
|
||||
/// </summary>
|
||||
public enum BorderKind
|
||||
{
|
||||
/// <summary>
|
||||
/// No border.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A square border.
|
||||
/// </summary>
|
||||
Square = 1,
|
||||
|
||||
/// <summary>
|
||||
/// An old school ASCII border.
|
||||
/// </summary>
|
||||
Ascii = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A rounded border.
|
||||
/// </summary>
|
||||
Rounded = 3,
|
||||
}
|
||||
}
|
||||
98
src/Spectre.Console/Composition/BorderPart.cs
Normal file
98
src/Spectre.Console/Composition/BorderPart.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the different border parts.
|
||||
/// </summary>
|
||||
public enum BorderPart
|
||||
{
|
||||
/// <summary>
|
||||
/// The top left part of a header.
|
||||
/// </summary>
|
||||
HeaderTopLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The top part of a header.
|
||||
/// </summary>
|
||||
HeaderTop,
|
||||
|
||||
/// <summary>
|
||||
/// The top separator part of a header.
|
||||
/// </summary>
|
||||
HeaderTopSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The top right part of a header.
|
||||
/// </summary>
|
||||
HeaderTopRight,
|
||||
|
||||
/// <summary>
|
||||
/// The left part of a header.
|
||||
/// </summary>
|
||||
HeaderLeft,
|
||||
|
||||
/// <summary>
|
||||
/// A header separator.
|
||||
/// </summary>
|
||||
HeaderSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The right part of a header.
|
||||
/// </summary>
|
||||
HeaderRight,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom left part of a header.
|
||||
/// </summary>
|
||||
HeaderBottomLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom part of a header.
|
||||
/// </summary>
|
||||
HeaderBottom,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom separator part of a header.
|
||||
/// </summary>
|
||||
HeaderBottomSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom right part of a header.
|
||||
/// </summary>
|
||||
HeaderBottomRight,
|
||||
|
||||
/// <summary>
|
||||
/// The left part of a cell.
|
||||
/// </summary>
|
||||
CellLeft,
|
||||
|
||||
/// <summary>
|
||||
/// A cell separator.
|
||||
/// </summary>
|
||||
CellSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The right part of a cell.
|
||||
/// </summary>
|
||||
CellRight,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom left part of a footer.
|
||||
/// </summary>
|
||||
FooterBottomLeft,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom part of a footer.
|
||||
/// </summary>
|
||||
FooterBottom,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom separator part of a footer.
|
||||
/// </summary>
|
||||
FooterBottomSeparator,
|
||||
|
||||
/// <summary>
|
||||
/// The bottom right part of a footer.
|
||||
/// </summary>
|
||||
FooterBottomRight,
|
||||
}
|
||||
}
|
||||
37
src/Spectre.Console/Composition/Borders/AsciiBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/AsciiBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an old school ASCII border.
|
||||
/// </summary>
|
||||
public sealed class AsciiBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
BorderPart.HeaderTopLeft => "+",
|
||||
BorderPart.HeaderTop => "-",
|
||||
BorderPart.HeaderTopSeparator => "-",
|
||||
BorderPart.HeaderTopRight => "+",
|
||||
BorderPart.HeaderLeft => "|",
|
||||
BorderPart.HeaderSeparator => "|",
|
||||
BorderPart.HeaderRight => "|",
|
||||
BorderPart.HeaderBottomLeft => "|",
|
||||
BorderPart.HeaderBottom => "-",
|
||||
BorderPart.HeaderBottomSeparator => "+",
|
||||
BorderPart.HeaderBottomRight => "|",
|
||||
BorderPart.CellLeft => "|",
|
||||
BorderPart.CellSeparator => "|",
|
||||
BorderPart.CellRight => "|",
|
||||
BorderPart.FooterBottomLeft => "+",
|
||||
BorderPart.FooterBottom => "-",
|
||||
BorderPart.FooterBottomSeparator => "-",
|
||||
BorderPart.FooterBottomRight => "+",
|
||||
_ => throw new InvalidOperationException("Unknown box part."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Spectre.Console/Composition/Borders/NoBorder.cs
Normal file
14
src/Spectre.Console/Composition/Borders/NoBorder.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an invisible border.
|
||||
/// </summary>
|
||||
public sealed class NoBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Spectre.Console/Composition/Borders/RoundedBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/RoundedBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a rounded border.
|
||||
/// </summary>
|
||||
public sealed class RoundedBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
BorderPart.HeaderTopLeft => "╭",
|
||||
BorderPart.HeaderTop => "─",
|
||||
BorderPart.HeaderTopSeparator => "┬",
|
||||
BorderPart.HeaderTopRight => "╮",
|
||||
BorderPart.HeaderLeft => "│",
|
||||
BorderPart.HeaderSeparator => "│",
|
||||
BorderPart.HeaderRight => "│",
|
||||
BorderPart.HeaderBottomLeft => "├",
|
||||
BorderPart.HeaderBottom => "─",
|
||||
BorderPart.HeaderBottomSeparator => "┼",
|
||||
BorderPart.HeaderBottomRight => "┤",
|
||||
BorderPart.CellLeft => "│",
|
||||
BorderPart.CellSeparator => "│",
|
||||
BorderPart.CellRight => "│",
|
||||
BorderPart.FooterBottomLeft => "╰",
|
||||
BorderPart.FooterBottom => "─",
|
||||
BorderPart.FooterBottomSeparator => "┴",
|
||||
BorderPart.FooterBottomRight => "╯",
|
||||
_ => throw new InvalidOperationException("Unknown box part."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Spectre.Console/Composition/Borders/SquareBorder.cs
Normal file
37
src/Spectre.Console/Composition/Borders/SquareBorder.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a square border.
|
||||
/// </summary>
|
||||
public sealed class SquareBorder : Border
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBoxPart(BorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
BorderPart.HeaderTopLeft => "┌",
|
||||
BorderPart.HeaderTop => "─",
|
||||
BorderPart.HeaderTopSeparator => "┬",
|
||||
BorderPart.HeaderTopRight => "┐",
|
||||
BorderPart.HeaderLeft => "│",
|
||||
BorderPart.HeaderSeparator => "│",
|
||||
BorderPart.HeaderRight => "│",
|
||||
BorderPart.HeaderBottomLeft => "├",
|
||||
BorderPart.HeaderBottom => "─",
|
||||
BorderPart.HeaderBottomSeparator => "┼",
|
||||
BorderPart.HeaderBottomRight => "┤",
|
||||
BorderPart.CellLeft => "│",
|
||||
BorderPart.CellSeparator => "│",
|
||||
BorderPart.CellRight => "│",
|
||||
BorderPart.FooterBottomLeft => "└",
|
||||
BorderPart.FooterBottom => "─",
|
||||
BorderPart.FooterBottomSeparator => "┴",
|
||||
BorderPart.FooterBottomRight => "┘",
|
||||
_ => throw new InvalidOperationException("Unknown box part."),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Spectre.Console/Composition/IRenderable.cs
Normal file
26
src/Spectre.Console/Composition/IRenderable.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents something that can be rendered to the console.
|
||||
/// </summary>
|
||||
public interface IRenderable
|
||||
{
|
||||
/// <summary>
|
||||
/// Measures the renderable object.
|
||||
/// </summary>
|
||||
/// <param name="context">The render context.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>The minimum and maximum width of the object.</returns>
|
||||
Measurement Measure(RenderContext context, int maxWidth);
|
||||
|
||||
/// <summary>
|
||||
/// Renders the object.
|
||||
/// </summary>
|
||||
/// <param name="context">The render context.</param>
|
||||
/// <param name="maxWidth">The maximum allowed width.</param>
|
||||
/// <returns>A collection of segments.</returns>
|
||||
IEnumerable<Segment> Render(RenderContext context, int maxWidth);
|
||||
}
|
||||
}
|
||||
23
src/Spectre.Console/Composition/Justify.cs
Normal file
23
src/Spectre.Console/Composition/Justify.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents text justification.
|
||||
/// </summary>
|
||||
public enum Justify
|
||||
{
|
||||
/// <summary>
|
||||
/// Left aligned.
|
||||
/// </summary>
|
||||
Left = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Right aligned.
|
||||
/// </summary>
|
||||
Right = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Centered.
|
||||
/// </summary>
|
||||
Center = 2,
|
||||
}
|
||||
}
|
||||
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
77
src/Spectre.Console/Composition/Measurement.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a measurement.
|
||||
/// </summary>
|
||||
public struct Measurement : IEquatable<Measurement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the minimum width.
|
||||
/// </summary>
|
||||
public int Min { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum width.
|
||||
/// </summary>
|
||||
public int Max { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Measurement"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum width.</param>
|
||||
/// <param name="max">The maximum width.</param>
|
||||
public Measurement(int min, int max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Measurement measurement && Equals(measurement);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Min.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Max.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Measurement other)
|
||||
{
|
||||
return Min == other.Min && Max == other.Max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator ==(Measurement left, Measurement right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Measurement"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first measurement instance to compare.</param>
|
||||
/// <param name="right">The second measurement instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two measurements are not equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator !=(Measurement left, Measurement right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/Spectre.Console/Composition/Padding.cs
Normal file
86
src/Spectre.Console/Composition/Padding.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a measurement.
|
||||
/// </summary>
|
||||
public struct Padding : IEquatable<Padding>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the left padding.
|
||||
/// </summary>
|
||||
public int Left { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right padding.
|
||||
/// </summary>
|
||||
public int Right { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Padding"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="left">The left padding.</param>
|
||||
/// <param name="right">The right padding.</param>
|
||||
public Padding(int left, int right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Padding padding && Equals(padding);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = (int)2166136261;
|
||||
hash = (hash * 16777619) ^ Left.GetHashCode();
|
||||
hash = (hash * 16777619) ^ Right.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(Padding other)
|
||||
{
|
||||
return Left == other.Left && Right == other.Right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Padding"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="Padding"/> instance to compare.</param>
|
||||
/// <param name="right">The second <see cref="Padding"/> instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two instances are equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator ==(Padding left, Padding right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Padding"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="Padding"/> instance to compare.</param>
|
||||
/// <param name="right">The second <see cref="Padding"/> instance to compare.</param>
|
||||
/// <returns><c>true</c> if the two instances are not equal, otherwise <c>false</c>.</returns>
|
||||
public static bool operator !=(Padding left, Padding right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the horizontal padding.
|
||||
/// </summary>
|
||||
/// <returns>The horizontal padding.</returns>
|
||||
public int GetHorizontalPadding()
|
||||
{
|
||||
return Left + Right;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/Spectre.Console/Composition/RenderContext.cs
Normal file
54
src/Spectre.Console/Composition/RenderContext.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a render context.
|
||||
/// </summary>
|
||||
public sealed class RenderContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the console's output encoding.
|
||||
/// </summary>
|
||||
public Encoding Encoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this a legacy console (i.e. cmd.exe).
|
||||
/// </summary>
|
||||
public bool LegacyConsole { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not unicode is supported.
|
||||
/// </summary>
|
||||
public bool Unicode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current justification.
|
||||
/// </summary>
|
||||
public Justify? Justification { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RenderContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The console's output encoding.</param>
|
||||
/// <param name="legacyConsole">A value indicating whether or not this a legacy console (i.e. cmd.exe).</param>
|
||||
/// <param name="justification">The justification to use when rendering.</param>
|
||||
public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null)
|
||||
{
|
||||
Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding));
|
||||
LegacyConsole = legacyConsole;
|
||||
Justification = justification;
|
||||
Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new context with the specified justification.
|
||||
/// </summary>
|
||||
/// <param name="justification">The justification.</param>
|
||||
/// <returns>A new <see cref="RenderContext"/> instance with the specified justification.</returns>
|
||||
public RenderContext WithJustification(Justify? justification)
|
||||
{
|
||||
return new RenderContext(Encoding, LegacyConsole, justification);
|
||||
}
|
||||
}
|
||||
}
|
||||
245
src/Spectre.Console/Composition/Segment.cs
Normal file
245
src/Spectre.Console/Composition/Segment.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
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 a value indicating whether or not this is a whitespace
|
||||
/// that should be preserved but not taken into account when
|
||||
/// layouting text.
|
||||
/// </summary>
|
||||
public bool IsWhiteSpace { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segment style.
|
||||
/// </summary>
|
||||
public Style Style { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a segment representing a line break.
|
||||
/// </summary>
|
||||
public static Segment LineBreak { get; } = new Segment("\n", Style.Plain, true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an empty segment.
|
||||
/// </summary>
|
||||
public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain);
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
Text = text.NormalizeLineEndings();
|
||||
Style = style;
|
||||
IsLineBreak = lineBreak;
|
||||
IsWhiteSpace = string.IsNullOrWhiteSpace(text);
|
||||
}
|
||||
|
||||
/// <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.Width + segment.Text.Length > maxWidth)
|
||||
{
|
||||
var diff = -(maxWidth - (line.Width + 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.Width > 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.Width > 0)
|
||||
{
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
}
|
||||
|
||||
text = string.Concat(parts.Skip(1).Take(parts.Length - 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
text = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.Add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
if (line.Count > 0)
|
||||
{
|
||||
lines.Add(line);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.Count < cellHeight)
|
||||
{
|
||||
while (cell.Count != cellHeight)
|
||||
{
|
||||
cell.Add(new SegmentLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Spectre.Console/Composition/SegmentLine.cs
Normal file
38
src/Spectre.Console/Composition/SegmentLine.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a collection of segments.
|
||||
/// </summary>
|
||||
[SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")]
|
||||
public sealed class SegmentLine : List<Segment>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the width of the line.
|
||||
/// </summary>
|
||||
public int Width => this.Sum(line => line.Text.Length);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cell width of the segment line.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding to use.</param>
|
||||
/// <returns>The cell width of the segment line.</returns>
|
||||
public int CellWidth(Encoding encoding)
|
||||
{
|
||||
return this.Sum(line => line.CellLength(encoding));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preprends a segment to the line.
|
||||
/// </summary>
|
||||
/// <param name="segment">The segment to prepend.</param>
|
||||
public void Prepend(Segment segment)
|
||||
{
|
||||
Insert(0, segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Spectre.Console/Composition/SegmentLineEnumerator.cs
Normal file
25
src/Spectre.Console/Composition/SegmentLineEnumerator.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
internal sealed class SegmentLineEnumerator : IEnumerable<Segment>
|
||||
{
|
||||
private readonly List<SegmentLine> _lines;
|
||||
|
||||
public SegmentLineEnumerator(List<SegmentLine> lines)
|
||||
{
|
||||
_lines = lines;
|
||||
}
|
||||
|
||||
public IEnumerator<Segment> GetEnumerator()
|
||||
{
|
||||
return new SegmentLineIterator(_lines);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
99
src/Spectre.Console/Composition/SegmentLineIterator.cs
Normal file
99
src/Spectre.Console/Composition/SegmentLineIterator.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Composition
|
||||
{
|
||||
internal sealed class SegmentLineIterator : IEnumerator<Segment>
|
||||
{
|
||||
private readonly List<SegmentLine> _lines;
|
||||
private int _currentLine;
|
||||
private int _currentIndex;
|
||||
private bool _lineBreakEmitted;
|
||||
|
||||
public Segment Current { get; private set; }
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
public SegmentLineIterator(List<SegmentLine> lines)
|
||||
{
|
||||
_currentLine = 0;
|
||||
_currentIndex = -1;
|
||||
_lines = lines;
|
||||
|
||||
Current = Segment.Empty;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_currentLine > _lines.Count - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentIndex += 1;
|
||||
|
||||
// Did we go past the end of the line?
|
||||
if (_currentIndex > _lines[_currentLine].Count - 1)
|
||||
{
|
||||
// We haven't just emitted a line break?
|
||||
if (!_lineBreakEmitted)
|
||||
{
|
||||
// Got any more lines?
|
||||
if (_currentIndex + 1 > _lines[_currentLine].Count - 1)
|
||||
{
|
||||
// Only emit a line break if the next one isn't a line break.
|
||||
if ((_currentLine + 1 <= _lines.Count - 1)
|
||||
&& _lines[_currentLine + 1].Count > 0
|
||||
&& !_lines[_currentLine + 1][0].IsLineBreak)
|
||||
{
|
||||
_lineBreakEmitted = true;
|
||||
Current = Segment.LineBreak;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Increase the line and reset the index.
|
||||
_currentLine += 1;
|
||||
_currentIndex = 0;
|
||||
|
||||
_lineBreakEmitted = false;
|
||||
|
||||
// No more lines?
|
||||
if (_currentLine > _lines.Count - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Nothing on the line?
|
||||
while (_currentIndex > _lines[_currentLine].Count - 1)
|
||||
{
|
||||
_currentLine += 1;
|
||||
_currentIndex = 0;
|
||||
|
||||
if (_currentLine > _lines.Count - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the flag
|
||||
_lineBreakEmitted = false;
|
||||
|
||||
Current = _lines[_currentLine][_currentIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentLine = 0;
|
||||
_currentIndex = -1;
|
||||
|
||||
Current = Segment.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
src/Spectre.Console/Composition/Widgets/Grid.cs
Normal file
141
src/Spectre.Console/Composition/Widgets/Grid.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid.
|
||||
/// </summary>
|
||||
public sealed class Grid : IRenderable
|
||||
{
|
||||
private readonly Table _table;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Grid"/> class.
|
||||
/// </summary>
|
||||
public Grid()
|
||||
{
|
||||
_table = new Table
|
||||
{
|
||||
Border = BorderKind.None,
|
||||
ShowHeaders = false,
|
||||
IsGrid = true,
|
||||
PadRightCell = false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)_table).Measure(context, maxWidth);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(RenderContext context, int width)
|
||||
{
|
||||
return ((IRenderable)_table).Render(context, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
public void AddColumn()
|
||||
{
|
||||
AddColumn(new GridColumn());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(GridColumn column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
if (_table.RowCount > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot add new columns to grid with existing rows.");
|
||||
}
|
||||
|
||||
// Only pad the most right cell if we've explicitly set a padding.
|
||||
_table.PadRightCell = column.Padding != null;
|
||||
|
||||
_table.AddColumn(new TableColumn(string.Empty)
|
||||
{
|
||||
Width = column.Width,
|
||||
NoWrap = column.NoWrap,
|
||||
Padding = column.Padding ?? new Padding(0, 2),
|
||||
Alignment = column.Alignment,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of columns to add.</param>
|
||||
public void AddColumns(int count)
|
||||
{
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
AddColumn(new GridColumn());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the grid.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddColumns(params GridColumn[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
foreach (var column in columns)
|
||||
{
|
||||
AddColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an empty row to the grid.
|
||||
/// </summary>
|
||||
public void AddEmptyRow()
|
||||
{
|
||||
var columns = new string[_table.ColumnCount];
|
||||
Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = string.Empty);
|
||||
AddRow(columns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new row to the grid.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddRow(params string[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
if (columns.Length < _table.ColumnCount)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are less than the number of grid columns.");
|
||||
}
|
||||
|
||||
if (columns.Length > _table.ColumnCount)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are greater than the number of grid columns.");
|
||||
}
|
||||
|
||||
_table.AddRow(columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Spectre.Console/Composition/Widgets/GridColumn.cs
Normal file
30
src/Spectre.Console/Composition/Widgets/GridColumn.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid column.
|
||||
/// </summary>
|
||||
public sealed class GridColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding of the column.
|
||||
/// </summary>
|
||||
public Padding? Padding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the column.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
}
|
||||
}
|
||||
133
src/Spectre.Console/Composition/Widgets/Panel.cs
Normal file
133
src/Spectre.Console/Composition/Widgets/Panel.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a panel which contains another renderable item.
|
||||
/// </summary>
|
||||
public sealed class Panel : IRenderable
|
||||
{
|
||||
private const int EdgeWidth = 2;
|
||||
|
||||
private readonly IRenderable _child;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to use
|
||||
/// a "safe" border on legacy consoles that might not be able
|
||||
/// to render non-ASCII characters. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
public bool SafeBorder { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of border to use.
|
||||
/// </summary>
|
||||
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the panel contents.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the panel should
|
||||
/// fit the available space. If <c>false</c>, the panel width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding.
|
||||
/// </summary>
|
||||
public Padding Padding { get; set; } = new Padding(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Panel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="content">The panel content.</param>
|
||||
public Panel(IRenderable content)
|
||||
{
|
||||
_child = content ?? throw new System.ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
var childWidth = _child.Measure(context, maxWidth);
|
||||
return new Measurement(childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Max + 2 + Padding.GetHorizontalPadding());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||
{
|
||||
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||
|
||||
var paddingWidth = Padding.GetHorizontalPadding();
|
||||
var childWidth = width - EdgeWidth - paddingWidth;
|
||||
|
||||
if (!Expand)
|
||||
{
|
||||
var measurement = _child.Measure(context, width - EdgeWidth - paddingWidth);
|
||||
childWidth = measurement.Max;
|
||||
}
|
||||
|
||||
var panelWidth = childWidth + paddingWidth;
|
||||
|
||||
// Panel top
|
||||
var result = new List<Segment>
|
||||
{
|
||||
new Segment(border.GetPart(BorderPart.HeaderTopLeft)),
|
||||
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)),
|
||||
new Segment(border.GetPart(BorderPart.HeaderTopRight)),
|
||||
new Segment("\n"),
|
||||
};
|
||||
|
||||
// Render the child.
|
||||
var childContext = context.WithJustification(Alignment);
|
||||
var childSegments = _child.Render(childContext, childWidth);
|
||||
|
||||
// Split the child segments into lines.
|
||||
foreach (var line in Segment.SplitLines(childSegments, panelWidth))
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||
|
||||
// Left padding
|
||||
if (Padding.Left > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', Padding.Left)));
|
||||
}
|
||||
|
||||
var content = new List<Segment>();
|
||||
content.AddRange(line);
|
||||
|
||||
// Do we need to pad the panel?
|
||||
var length = line.Sum(segment => segment.CellLength(context.Encoding));
|
||||
if (length < childWidth)
|
||||
{
|
||||
var diff = childWidth - length;
|
||||
content.Add(new Segment(new string(' ', diff)));
|
||||
}
|
||||
|
||||
result.AddRange(content);
|
||||
|
||||
// Right padding
|
||||
if (Padding.Right > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', Padding.Right)));
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||
result.Add(new Segment("\n"));
|
||||
}
|
||||
|
||||
// Panel bottom
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(new Segment("\n"));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
src/Spectre.Console/Composition/Widgets/Table.Calculations.cs
Normal file
125
src/Spectre.Console/Composition/Widgets/Table.Calculations.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed partial class Table
|
||||
{
|
||||
private const int EdgeCount = 2;
|
||||
|
||||
// Calculate the widths of each column, including padding, not including borders.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
|
||||
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
|
||||
{
|
||||
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth));
|
||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||
|
||||
var tableWidth = widths.Sum();
|
||||
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||
widths = CollapseWidths(widths, wrappable, maxWidth);
|
||||
tableWidth = widths.Sum();
|
||||
|
||||
// last resort, reduce columns evenly
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var excessWidth = tableWidth - maxWidth;
|
||||
widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths);
|
||||
tableWidth = widths.Sum();
|
||||
}
|
||||
}
|
||||
|
||||
if (tableWidth < maxWidth && ShouldExpand())
|
||||
{
|
||||
var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths);
|
||||
widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList();
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
// Reduce widths so that the total is less or equal to the max width.
|
||||
// Ported from Rich by Will McGugan, licensed under MIT.
|
||||
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442
|
||||
private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth)
|
||||
{
|
||||
var totalWidth = widths.Sum();
|
||||
var excessWidth = totalWidth - maxWidth;
|
||||
|
||||
if (wrappable.AnyTrue())
|
||||
{
|
||||
while (totalWidth != 0 && excessWidth > 0)
|
||||
{
|
||||
var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second))
|
||||
.Where(x => x.allowWrap)
|
||||
.Max(x => x.width);
|
||||
|
||||
var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 1).Max();
|
||||
var columnDifference = maxColumn - secondMaxColumn;
|
||||
|
||||
var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList();
|
||||
if (!ratios.Any(x => x != 0) || columnDifference == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList();
|
||||
widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths);
|
||||
|
||||
totalWidth = widths.Sum();
|
||||
excessWidth = totalWidth - maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return widths;
|
||||
}
|
||||
|
||||
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
|
||||
{
|
||||
var padding = column.Padding.GetHorizontalPadding();
|
||||
|
||||
// Predetermined width?
|
||||
if (column.Width != null)
|
||||
{
|
||||
return (column.Width.Value + padding, column.Width.Value + padding);
|
||||
}
|
||||
|
||||
var columnIndex = _columns.IndexOf(column);
|
||||
var rows = _rows.Select(row => row[columnIndex]);
|
||||
|
||||
var minWidths = new List<int>();
|
||||
var maxWidths = new List<int>();
|
||||
|
||||
// Include columns in measurement
|
||||
var measure = ((IRenderable)column.Text).Measure(options, maxWidth);
|
||||
minWidths.Add(measure.Min);
|
||||
maxWidths.Add(measure.Max);
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
measure = ((IRenderable)row).Measure(options, maxWidth);
|
||||
minWidths.Add(measure.Min);
|
||||
maxWidths.Add(measure.Max);
|
||||
}
|
||||
|
||||
return (minWidths.Count > 0 ? minWidths.Max() : padding,
|
||||
maxWidths.Count > 0 ? maxWidths.Max() : maxWidth);
|
||||
}
|
||||
|
||||
private int GetExtraWidth(bool includePadding)
|
||||
{
|
||||
var separators = _columns.Count - 1;
|
||||
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
|
||||
return separators + EdgeCount + padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
386
src/Spectre.Console/Composition/Widgets/Table.cs
Normal file
386
src/Spectre.Console/Composition/Widgets/Table.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table.
|
||||
/// </summary>
|
||||
public sealed partial class Table : IRenderable
|
||||
{
|
||||
private readonly List<TableColumn> _columns;
|
||||
private readonly List<List<Text>> _rows;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of columns in the table.
|
||||
/// </summary>
|
||||
public int ColumnCount => _columns.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of rows in the table.
|
||||
/// </summary>
|
||||
public int RowCount => _rows.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of border to use.
|
||||
/// </summary>
|
||||
public BorderKind Border { get; set; } = BorderKind.Square;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not table headers should be shown.
|
||||
/// </summary>
|
||||
public bool ShowHeaders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the table should
|
||||
/// fit the available space. If <c>false</c>, the table width will be
|
||||
/// auto calculated. Defaults to <c>false</c>.
|
||||
/// </summary>
|
||||
public bool Expand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the table.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to use
|
||||
/// a "safe" border on legacy consoles that might not be able
|
||||
/// to render non-ASCII characters. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
public bool SafeBorder { get; set; } = true;
|
||||
|
||||
// Whether this is a grid or not.
|
||||
internal bool IsGrid { get; set; }
|
||||
|
||||
// Whether or not the most right cell should be padded.
|
||||
// This is almost always the case, unless we're rendering
|
||||
// a grid without explicit padding in the last cell.
|
||||
internal bool PadRightCell { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Table"/> class.
|
||||
/// </summary>
|
||||
public Table()
|
||||
{
|
||||
_columns = new List<TableColumn>();
|
||||
_rows = new List<List<Text>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the table.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(string column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
AddColumn(new TableColumn(column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a column to the table.
|
||||
/// </summary>
|
||||
/// <param name="column">The column to add.</param>
|
||||
public void AddColumn(TableColumn column)
|
||||
{
|
||||
if (column is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(column));
|
||||
}
|
||||
|
||||
if (_rows.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot add new columns to table with existing rows.");
|
||||
}
|
||||
|
||||
_columns.Add(column);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple columns to the table.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddColumns(params string[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
foreach (var column in columns)
|
||||
{
|
||||
AddColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple columns to the table.
|
||||
/// </summary>
|
||||
/// <param name="columns">The columns to add.</param>
|
||||
public void AddColumns(params TableColumn[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
foreach (var column in columns)
|
||||
{
|
||||
AddColumn(column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an empty row to the table.
|
||||
/// </summary>
|
||||
public void AddEmptyRow()
|
||||
{
|
||||
var columns = new string[ColumnCount];
|
||||
Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = string.Empty);
|
||||
AddRow(columns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a row to the table.
|
||||
/// </summary>
|
||||
/// <param name="columns">The row columns to add.</param>
|
||||
public void AddRow(params string[] columns)
|
||||
{
|
||||
if (columns is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(columns));
|
||||
}
|
||||
|
||||
if (columns.Length < _columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are less than the number of table columns.");
|
||||
}
|
||||
|
||||
if (columns.Length > _columns.Count)
|
||||
{
|
||||
throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
|
||||
}
|
||||
|
||||
_rows.Add(columns.Select(column => Text.Markup(column)).ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Measurement IRenderable.Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (Width != null)
|
||||
{
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
var measurements = _columns.Select(column => MeasureColumn(column, context, maxWidth)).ToList();
|
||||
var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true);
|
||||
var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true);
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerable<Segment> IRenderable.Render(RenderContext context, int width)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder);
|
||||
|
||||
var showBorder = Border != BorderKind.None;
|
||||
var hideBorder = Border == BorderKind.None;
|
||||
var hasRows = _rows.Count > 0;
|
||||
|
||||
var maxWidth = width;
|
||||
if (Width != null)
|
||||
{
|
||||
maxWidth = Math.Min(Width.Value, maxWidth);
|
||||
}
|
||||
|
||||
maxWidth -= GetExtraWidth(includePadding: true);
|
||||
|
||||
// Calculate the column and table widths
|
||||
var columnWidths = CalculateColumnWidths(context, maxWidth);
|
||||
|
||||
// Update the table width.
|
||||
width = columnWidths.Sum() + GetExtraWidth(includePadding: true);
|
||||
|
||||
var rows = new List<List<Text>>();
|
||||
if (ShowHeaders)
|
||||
{
|
||||
// Add columns to top of rows
|
||||
rows.Add(new List<Text>(_columns.Select(c => c.Text)));
|
||||
}
|
||||
|
||||
// Add rows.
|
||||
rows.AddRange(_rows);
|
||||
|
||||
// Iterate all rows
|
||||
var result = new List<Segment>();
|
||||
foreach (var (index, firstRow, lastRow, row) in rows.Enumerate())
|
||||
{
|
||||
var cellHeight = 1;
|
||||
|
||||
// Get the list of cells for the row and calculate the cell height
|
||||
var cells = new List<List<SegmentLine>>();
|
||||
foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate())
|
||||
{
|
||||
var justification = _columns[columnIndex].Alignment;
|
||||
var childContext = context.WithJustification(justification);
|
||||
|
||||
var lines = Segment.SplitLines(((IRenderable)cell).Render(childContext, rowWidth));
|
||||
cellHeight = Math.Max(cellHeight, lines.Count);
|
||||
cells.Add(lines);
|
||||
}
|
||||
|
||||
// Show top of header?
|
||||
if (firstRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft)));
|
||||
foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
var padding = _columns[columnIndex].Padding;
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight)));
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
|
||||
// Iterate through each cell row
|
||||
foreach (var cellRowIndex in Enumerable.Range(0, cellHeight))
|
||||
{
|
||||
// Make cells the same shape
|
||||
cells = Segment.MakeSameHeight(cellHeight, cells);
|
||||
|
||||
foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate())
|
||||
{
|
||||
if (firstCell && showBorder)
|
||||
{
|
||||
// Show left column edge
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellLeft)));
|
||||
}
|
||||
|
||||
// Pad column on left side.
|
||||
if (showBorder || IsGrid)
|
||||
{
|
||||
var leftPadding = _columns[cellIndex].Padding.Left;
|
||||
if (leftPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', leftPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
// Add content
|
||||
result.AddRange(cell[cellRowIndex]);
|
||||
|
||||
// Pad cell content right
|
||||
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding));
|
||||
if (length < columnWidths[cellIndex])
|
||||
{
|
||||
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
|
||||
}
|
||||
|
||||
// Pad column on the right side
|
||||
if (showBorder || (hideBorder && !lastCell) || (hideBorder && lastCell && IsGrid && PadRightCell))
|
||||
{
|
||||
var rightPadding = _columns[cellIndex].Padding.Right;
|
||||
if (rightPadding > 0)
|
||||
{
|
||||
result.Add(new Segment(new string(' ', rightPadding)));
|
||||
}
|
||||
}
|
||||
|
||||
if (lastCell && showBorder)
|
||||
{
|
||||
// Add right column edge
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellRight)));
|
||||
}
|
||||
else if (showBorder)
|
||||
{
|
||||
// Add column separator
|
||||
result.Add(new Segment(border.GetPart(BorderPart.CellSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
|
||||
// Show header separator?
|
||||
if (firstRow && showBorder && ShowHeaders && hasRows)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
var padding = _columns[columnIndex].Padding;
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight)));
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
|
||||
// Show bottom of footer?
|
||||
if (lastRow && showBorder)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft)));
|
||||
foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate())
|
||||
{
|
||||
var padding = _columns[columnIndex].Padding;
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left))); // Left padding
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth)));
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right))); // Right padding
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator)));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight)));
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool ShouldExpand()
|
||||
{
|
||||
return Expand || Width != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/Spectre.Console/Composition/Widgets/TableColumn.cs
Normal file
50
src/Spectre.Console/Composition/Widgets/TableColumn.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table column.
|
||||
/// </summary>
|
||||
public sealed class TableColumn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the text associated with the column.
|
||||
/// </summary>
|
||||
public Text Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the column.
|
||||
/// If <c>null</c>, the column will adapt to it's contents.
|
||||
/// </summary>
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the padding of the column.
|
||||
/// </summary>
|
||||
public Padding Padding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether wrapping of
|
||||
/// text within the column should be prevented.
|
||||
/// </summary>
|
||||
public bool NoWrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alignment of the column.
|
||||
/// </summary>
|
||||
public Justify? Alignment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TableColumn"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The table column text.</param>
|
||||
public TableColumn(string text)
|
||||
{
|
||||
Text = Text.Markup(text ?? throw new ArgumentNullException(nameof(text)));
|
||||
Width = null;
|
||||
Padding = new Padding(1, 1);
|
||||
NoWrap = false;
|
||||
Alignment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
270
src/Spectre.Console/Composition/Widgets/Text.cs
Normal file
270
src/Spectre.Console/Composition/Widgets/Text.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a piece of text.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{_text,nq}")]
|
||||
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
|
||||
public sealed class Text : IRenderable
|
||||
{
|
||||
private readonly List<SegmentLine> _lines;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text alignment.
|
||||
/// </summary>
|
||||
public Justify Alignment { get; set; } = Justify.Left;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Text"/> class.
|
||||
/// </summary>
|
||||
public Text()
|
||||
{
|
||||
_lines = new List<SegmentLine>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Text"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="style">The style of the text.</param>
|
||||
public Text(string text, Style? style = null)
|
||||
: this()
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
Append(text, style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Text"/> instance representing
|
||||
/// the specified markup text.
|
||||
/// </summary>
|
||||
/// <param name="text">The markup text.</param>
|
||||
/// <param name="style">The text style.</param>
|
||||
/// <returns>a <see cref="Text"/> instance representing the specified markup text.</returns>
|
||||
public static Text Markup(string text, Style? style = null)
|
||||
{
|
||||
var result = MarkupParser.Parse(text, style ?? Style.Plain);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Measurement Measure(RenderContext context, int maxWidth)
|
||||
{
|
||||
if (_lines.Count == 0)
|
||||
{
|
||||
return new Measurement(0, 0);
|
||||
}
|
||||
|
||||
var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding)));
|
||||
var max = _lines.Max(x => x.CellWidth(context.Encoding));
|
||||
|
||||
return new Measurement(min, max);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (_lines.Count == 0)
|
||||
{
|
||||
return Array.Empty<Segment>();
|
||||
}
|
||||
|
||||
var justification = context.Justification ?? Alignment;
|
||||
|
||||
var lines = SplitLines(context, maxWidth);
|
||||
foreach (var (_, _, last, line) in lines.Enumerate())
|
||||
{
|
||||
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding));
|
||||
if (length < maxWidth)
|
||||
{
|
||||
// Justify right side
|
||||
if (justification == Justify.Right)
|
||||
{
|
||||
var diff = maxWidth - length;
|
||||
line.Prepend(new Segment(new string(' ', diff)));
|
||||
}
|
||||
else if (justification == Justify.Center)
|
||||
{
|
||||
// Left side.
|
||||
var diff = (maxWidth - length) / 2;
|
||||
line.Prepend(new Segment(new string(' ', diff)));
|
||||
|
||||
// Right side
|
||||
line.Add(new Segment(new string(' ', diff)));
|
||||
var remainder = (maxWidth - length) % 2;
|
||||
if (remainder != 0)
|
||||
{
|
||||
line.Add(new Segment(new string(' ', remainder)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SegmentLineEnumerator(lines);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a piece of text.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to append.</param>
|
||||
/// <param name="style">The style of the appended text.</param>
|
||||
public void Append(string text, Style? style = null)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
foreach (var (_, first, last, part) in text.SplitLines().Enumerate())
|
||||
{
|
||||
var current = part;
|
||||
if (string.IsNullOrEmpty(current) && last)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (first)
|
||||
{
|
||||
var line = _lines.LastOrDefault();
|
||||
if (line == null)
|
||||
{
|
||||
_lines.Add(new SegmentLine());
|
||||
line = _lines.Last();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(current))
|
||||
{
|
||||
line.Add(Segment.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var span in current.SplitWords())
|
||||
{
|
||||
line.Add(new Segment(span, style ?? Style.Plain));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var line = new SegmentLine();
|
||||
|
||||
if (string.IsNullOrEmpty(current))
|
||||
{
|
||||
line.Add(Segment.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var span in current.SplitWords())
|
||||
{
|
||||
line.Add(new Segment(span, style ?? Style.Plain));
|
||||
}
|
||||
}
|
||||
|
||||
_lines.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SegmentLine> Clone()
|
||||
{
|
||||
var result = new List<SegmentLine>();
|
||||
|
||||
foreach (var line in _lines)
|
||||
{
|
||||
var newLine = new SegmentLine();
|
||||
foreach (var segment in line)
|
||||
{
|
||||
newLine.Add(segment);
|
||||
}
|
||||
|
||||
result.Add(newLine);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<SegmentLine> SplitLines(RenderContext context, int maxWidth)
|
||||
{
|
||||
if (_lines.Max(x => x.CellWidth(context.Encoding)) <= maxWidth)
|
||||
{
|
||||
return Clone();
|
||||
}
|
||||
|
||||
var lines = new List<SegmentLine>();
|
||||
var line = new SegmentLine();
|
||||
|
||||
var newLine = true;
|
||||
using (var iterator = new SegmentLineIterator(_lines))
|
||||
{
|
||||
while (iterator.MoveNext())
|
||||
{
|
||||
var current = iterator.Current;
|
||||
if (current == null)
|
||||
{
|
||||
throw new InvalidOperationException("Iterator returned empty segment.");
|
||||
}
|
||||
|
||||
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
|
||||
{
|
||||
newLine = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
newLine = false;
|
||||
|
||||
if (current.IsLineBreak)
|
||||
{
|
||||
line.Add(current);
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
newLine = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
var length = current.CellLength(context.Encoding);
|
||||
if (line.CellWidth(context.Encoding) + length > maxWidth)
|
||||
{
|
||||
line.Add(Segment.Empty);
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
newLine = true;
|
||||
}
|
||||
|
||||
if (newLine && current.IsWhiteSpace)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newLine = false;
|
||||
|
||||
line.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
// Flush remaining.
|
||||
if (line.Count > 0)
|
||||
{
|
||||
lines.Add(line);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/Spectre.Console/Composition/Widgets/TextExtensions.cs
Normal file
27
src/Spectre.Console/Composition/Widgets/TextExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="Text"/>.
|
||||
/// </summary>
|
||||
public static class TextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the text alignment.
|
||||
/// </summary>
|
||||
/// <param name="text">The <see cref="Text"/> instance.</param>
|
||||
/// <param name="alignment">The text alignment.</param>
|
||||
/// <returns>The same <see cref="Text"/> instance.</returns>
|
||||
public static Text WithAlignment(this Text text, Justify alignment)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.Alignment = alignment;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
54
src/Spectre.Console/ConsoleExtensions.Rendering.cs
Normal file
54
src/Spectre.Console/ConsoleExtensions.Rendering.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Spectre.Console.Composition;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||
/// </summary>
|
||||
public static partial class ConsoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the specified object to the console.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to render to.</param>
|
||||
/// <param name="renderable">The object to render.</param>
|
||||
public static void Render(this IAnsiConsole console, IRenderable renderable)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
if (renderable is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderable));
|
||||
}
|
||||
|
||||
var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole);
|
||||
|
||||
using (console.PushStyle(Style.Plain))
|
||||
{
|
||||
var current = Style.Plain;
|
||||
foreach (var segment in renderable.Render(options, console.Width))
|
||||
{
|
||||
if (string.IsNullOrEmpty(segment.Text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!segment.Style.Equals(current))
|
||||
{
|
||||
console.Foreground = segment.Style.Foreground;
|
||||
console.Background = segment.Style.Background;
|
||||
console.Decoration = segment.Style.Decoration;
|
||||
current = segment.Style;
|
||||
}
|
||||
|
||||
console.Write(segment.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console
|
||||
public static partial class ConsoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets both colors and style for the console.
|
||||
/// Resets colors and text decorations.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to reset.</param>
|
||||
public static void Reset(this IAnsiConsole console)
|
||||
@@ -19,25 +19,25 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
console.ResetColors();
|
||||
console.ResetStyle();
|
||||
console.ResetDecoration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the current style back to the default one.
|
||||
/// Resets the current applied text decorations.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to reset the style for.</param>
|
||||
public static void ResetStyle(this IAnsiConsole console)
|
||||
/// <param name="console">The console to reset the text decorations for.</param>
|
||||
public static void ResetDecoration(this IAnsiConsole console)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
console.Style = Styles.None;
|
||||
console.Decoration = Decoration.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the foreground and background colors to the default ones.
|
||||
/// Resets the current applied foreground and background colors.
|
||||
/// </summary>
|
||||
/// <param name="console">The console to reset colors for.</param>
|
||||
public static void ResetColors(this IAnsiConsole console)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a style.
|
||||
/// Represents text decoration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Support for different styles is up to the terminal.
|
||||
/// Support for text decorations is up to the terminal.
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
public enum Styles
|
||||
[SuppressMessage("Naming", "CA1714:Flags enums should have plural names")]
|
||||
public enum Decoration
|
||||
{
|
||||
/// <summary>
|
||||
/// No style.
|
||||
/// No text decoration.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
@@ -8,7 +10,7 @@ namespace Spectre.Console
|
||||
/// <summary>
|
||||
/// Gets the console's capabilities.
|
||||
/// </summary>
|
||||
AnsiConsoleCapabilities Capabilities { get; }
|
||||
Capabilities Capabilities { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the buffer width of the console.
|
||||
@@ -21,9 +23,14 @@ namespace Spectre.Console
|
||||
int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current style.
|
||||
/// Gets the console output encoding.
|
||||
/// </summary>
|
||||
Styles Style { get; set; }
|
||||
Encoding Encoding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current text decoration.
|
||||
/// </summary>
|
||||
Decoration Decoration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current foreground.
|
||||
|
||||
@@ -7,11 +7,11 @@ namespace Spectre.Console.Internal
|
||||
public static string GetAnsi(
|
||||
ColorSystem system,
|
||||
string text,
|
||||
Styles style,
|
||||
Decoration decoration,
|
||||
Color foreground,
|
||||
Color background)
|
||||
{
|
||||
var codes = AnsiStyleBuilder.GetAnsiCodes(style);
|
||||
var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration);
|
||||
|
||||
// Got foreground?
|
||||
if (foreground != Color.Default)
|
||||
|
||||
@@ -2,52 +2,52 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class AnsiStyleBuilder
|
||||
internal static class AnsiDecorationBuilder
|
||||
{
|
||||
// TODO: Rewrite this to not yield
|
||||
public static IEnumerable<byte> GetAnsiCodes(Styles style)
|
||||
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
|
||||
{
|
||||
if ((style & Styles.Bold) != 0)
|
||||
if ((decoration & Decoration.Bold) != 0)
|
||||
{
|
||||
yield return 1;
|
||||
}
|
||||
|
||||
if ((style & Styles.Dim) != 0)
|
||||
if ((decoration & Decoration.Dim) != 0)
|
||||
{
|
||||
yield return 2;
|
||||
}
|
||||
|
||||
if ((style & Styles.Italic) != 0)
|
||||
if ((decoration & Decoration.Italic) != 0)
|
||||
{
|
||||
yield return 3;
|
||||
}
|
||||
|
||||
if ((style & Styles.Underline) != 0)
|
||||
if ((decoration & Decoration.Underline) != 0)
|
||||
{
|
||||
yield return 4;
|
||||
}
|
||||
|
||||
if ((style & Styles.SlowBlink) != 0)
|
||||
if ((decoration & Decoration.SlowBlink) != 0)
|
||||
{
|
||||
yield return 5;
|
||||
}
|
||||
|
||||
if ((style & Styles.RapidBlink) != 0)
|
||||
if ((decoration & Decoration.RapidBlink) != 0)
|
||||
{
|
||||
yield return 6;
|
||||
}
|
||||
|
||||
if ((style & Styles.Invert) != 0)
|
||||
if ((decoration & Decoration.Invert) != 0)
|
||||
{
|
||||
yield return 7;
|
||||
}
|
||||
|
||||
if ((style & Styles.Conceal) != 0)
|
||||
if ((decoration & Decoration.Conceal) != 0)
|
||||
{
|
||||
yield return 8;
|
||||
}
|
||||
|
||||
if ((style & Styles.Strikethrough) != 0)
|
||||
if ((decoration & Decoration.Strikethrough) != 0)
|
||||
{
|
||||
yield return 9;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class AnsiDetector
|
||||
{
|
||||
private static readonly Regex[] Regexes = new[]
|
||||
private static readonly Regex[] _regexes = new[]
|
||||
{
|
||||
new Regex("^xterm"), // xterm, PuTTY, Mintty
|
||||
new Regex("^rxvt"), // RXVT
|
||||
@@ -32,12 +32,12 @@ namespace Spectre.Console.Internal
|
||||
new Regex("bvterm"), // Bitvise SSH Client
|
||||
};
|
||||
|
||||
public static bool SupportsAnsi(bool upgrade)
|
||||
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool upgrade)
|
||||
{
|
||||
// Github action doesn't setup a correct PTY but supports ANSI.
|
||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
|
||||
{
|
||||
return true;
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
// Running on Windows?
|
||||
@@ -47,23 +47,24 @@ namespace Spectre.Console.Internal
|
||||
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
|
||||
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
return Windows.SupportsAnsi(upgrade);
|
||||
var supportsAnsi = Windows.SupportsAnsi(upgrade, out var legacyConsole);
|
||||
return (supportsAnsi, legacyConsole);
|
||||
}
|
||||
|
||||
// Check if the terminal is of type ANSI/VT100/xterm compatible.
|
||||
var term = Environment.GetEnvironmentVariable("TERM");
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
if (Regexes.Any(regex => regex.IsMatch(term)))
|
||||
if (_regexes.Any(regex => regex.IsMatch(term)))
|
||||
{
|
||||
return true;
|
||||
return (true, false);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return (false, true);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")]
|
||||
@@ -71,8 +72,10 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
private const int STD_OUTPUT_HANDLE = -11;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
|
||||
|
||||
@@ -89,12 +92,14 @@ namespace Spectre.Console.Internal
|
||||
public static extern uint GetLastError();
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public static bool SupportsAnsi(bool upgrade)
|
||||
public static bool SupportsAnsi(bool upgrade, out bool isLegacy)
|
||||
{
|
||||
isLegacy = false;
|
||||
|
||||
try
|
||||
{
|
||||
var @out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (!GetConsoleMode(@out, out uint mode))
|
||||
if (!GetConsoleMode(@out, out var mode))
|
||||
{
|
||||
// Could not get console mode.
|
||||
return false;
|
||||
@@ -102,6 +107,8 @@ namespace Spectre.Console.Internal
|
||||
|
||||
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
|
||||
{
|
||||
isLegacy = true;
|
||||
|
||||
if (!upgrade)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
@@ -8,8 +9,9 @@ namespace Spectre.Console.Internal
|
||||
private readonly TextWriter _out;
|
||||
private readonly ColorSystem _system;
|
||||
|
||||
public AnsiConsoleCapabilities Capabilities { get; }
|
||||
public Styles Style { get; set; }
|
||||
public Capabilities Capabilities { get; }
|
||||
public Encoding Encoding { get; }
|
||||
public Decoration Decoration { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
public Color Background { get; set; }
|
||||
|
||||
@@ -39,29 +41,16 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system)
|
||||
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
|
||||
{
|
||||
_out = @out ?? throw new ArgumentNullException(nameof(@out));
|
||||
_system = system;
|
||||
|
||||
Capabilities = new AnsiConsoleCapabilities(true, system);
|
||||
Capabilities = new Capabilities(true, system, legacyConsole);
|
||||
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
|
||||
Foreground = Color.Default;
|
||||
Background = Color.Default;
|
||||
Style = Styles.None;
|
||||
}
|
||||
|
||||
public void Reset(bool colors, bool styles)
|
||||
{
|
||||
if (colors)
|
||||
{
|
||||
Foreground = Color.Default;
|
||||
Background = Color.Default;
|
||||
}
|
||||
|
||||
if (styles)
|
||||
{
|
||||
Style = Styles.None;
|
||||
}
|
||||
Decoration = Decoration.None;
|
||||
}
|
||||
|
||||
public void Write(string text)
|
||||
@@ -71,12 +60,19 @@ namespace Spectre.Console.Internal
|
||||
return;
|
||||
}
|
||||
|
||||
_out.Write(AnsiBuilder.GetAnsi(
|
||||
_system,
|
||||
text,
|
||||
Style,
|
||||
Foreground,
|
||||
Background));
|
||||
var parts = text.NormalizeLineEndings().Split(new[] { '\n' });
|
||||
foreach (var (_, _, last, part) in parts.Enumerate())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
{
|
||||
_out.Write(AnsiBuilder.GetAnsi(_system, part, Decoration, Foreground, Background));
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
_out.Write(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
294
src/Spectre.Console/Internal/Colors/ColorPalette.Generated.cs
Normal file
294
src/Spectre.Console/Internal/Colors/ColorPalette.Generated.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated 2020-08-03 15:17
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
private static List<Color> GenerateLegacyPalette()
|
||||
{
|
||||
return new List<Color>
|
||||
{
|
||||
Color.Black,
|
||||
Color.Maroon,
|
||||
Color.Green,
|
||||
Color.Olive,
|
||||
Color.Navy,
|
||||
Color.Purple,
|
||||
Color.Teal,
|
||||
Color.Silver,
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)
|
||||
{
|
||||
return new List<Color>(legacy)
|
||||
{
|
||||
Color.Grey,
|
||||
Color.Red,
|
||||
Color.Lime,
|
||||
Color.Yellow,
|
||||
Color.Blue,
|
||||
Color.Fuchsia,
|
||||
Color.Aqua,
|
||||
Color.White,
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)
|
||||
{
|
||||
return new List<Color>(standard)
|
||||
{
|
||||
Color.Grey0,
|
||||
Color.NavyBlue,
|
||||
Color.DarkBlue,
|
||||
Color.Blue3,
|
||||
Color.Blue3_1,
|
||||
Color.Blue1,
|
||||
Color.DarkGreen,
|
||||
Color.DeepSkyBlue4,
|
||||
Color.DeepSkyBlue4_1,
|
||||
Color.DeepSkyBlue4_2,
|
||||
Color.DodgerBlue3,
|
||||
Color.DodgerBlue2,
|
||||
Color.Green4,
|
||||
Color.SpringGreen4,
|
||||
Color.Turquoise4,
|
||||
Color.DeepSkyBlue3,
|
||||
Color.DeepSkyBlue3_1,
|
||||
Color.DodgerBlue1,
|
||||
Color.Green3,
|
||||
Color.SpringGreen3,
|
||||
Color.DarkCyan,
|
||||
Color.LightSeaGreen,
|
||||
Color.DeepSkyBlue2,
|
||||
Color.DeepSkyBlue1,
|
||||
Color.Green3_1,
|
||||
Color.SpringGreen3_1,
|
||||
Color.SpringGreen2,
|
||||
Color.Cyan3,
|
||||
Color.DarkTurquoise,
|
||||
Color.Turquoise2,
|
||||
Color.Green1,
|
||||
Color.SpringGreen2_1,
|
||||
Color.SpringGreen1,
|
||||
Color.MediumSpringGreen,
|
||||
Color.Cyan2,
|
||||
Color.Cyan1,
|
||||
Color.DarkRed,
|
||||
Color.DeepPink4,
|
||||
Color.Purple4,
|
||||
Color.Purple4_1,
|
||||
Color.Purple3,
|
||||
Color.BlueViolet,
|
||||
Color.Orange4,
|
||||
Color.Grey37,
|
||||
Color.MediumPurple4,
|
||||
Color.SlateBlue3,
|
||||
Color.SlateBlue3_1,
|
||||
Color.RoyalBlue1,
|
||||
Color.Chartreuse4,
|
||||
Color.DarkSeaGreen4,
|
||||
Color.PaleTurquoise4,
|
||||
Color.SteelBlue,
|
||||
Color.SteelBlue3,
|
||||
Color.CornflowerBlue,
|
||||
Color.Chartreuse3,
|
||||
Color.DarkSeaGreen4_1,
|
||||
Color.CadetBlue,
|
||||
Color.CadetBlue_1,
|
||||
Color.SkyBlue3,
|
||||
Color.SteelBlue1,
|
||||
Color.Chartreuse3_1,
|
||||
Color.PaleGreen3,
|
||||
Color.SeaGreen3,
|
||||
Color.Aquamarine3,
|
||||
Color.MediumTurquoise,
|
||||
Color.SteelBlue1_1,
|
||||
Color.Chartreuse2,
|
||||
Color.SeaGreen2,
|
||||
Color.SeaGreen1,
|
||||
Color.SeaGreen1_1,
|
||||
Color.Aquamarine1,
|
||||
Color.DarkSlateGray2,
|
||||
Color.DarkRed_1,
|
||||
Color.DeepPink4_1,
|
||||
Color.DarkMagenta,
|
||||
Color.DarkMagenta_1,
|
||||
Color.DarkViolet,
|
||||
Color.Purple_1,
|
||||
Color.Orange4_1,
|
||||
Color.LightPink4,
|
||||
Color.Plum4,
|
||||
Color.MediumPurple3,
|
||||
Color.MediumPurple3_1,
|
||||
Color.SlateBlue1,
|
||||
Color.Yellow4,
|
||||
Color.Wheat4,
|
||||
Color.Grey53,
|
||||
Color.LightSlateGrey,
|
||||
Color.MediumPurple,
|
||||
Color.LightSlateBlue,
|
||||
Color.Yellow4_1,
|
||||
Color.DarkOliveGreen3,
|
||||
Color.DarkSeaGreen,
|
||||
Color.LightSkyBlue3,
|
||||
Color.LightSkyBlue3_1,
|
||||
Color.SkyBlue2,
|
||||
Color.Chartreuse2_1,
|
||||
Color.DarkOliveGreen3_1,
|
||||
Color.PaleGreen3_1,
|
||||
Color.DarkSeaGreen3,
|
||||
Color.DarkSlateGray3,
|
||||
Color.SkyBlue1,
|
||||
Color.Chartreuse1,
|
||||
Color.LightGreen,
|
||||
Color.LightGreen_1,
|
||||
Color.PaleGreen1,
|
||||
Color.Aquamarine1_1,
|
||||
Color.DarkSlateGray1,
|
||||
Color.Red3,
|
||||
Color.DeepPink4_2,
|
||||
Color.MediumVioletRed,
|
||||
Color.Magenta3,
|
||||
Color.DarkViolet_1,
|
||||
Color.Purple_2,
|
||||
Color.DarkOrange3,
|
||||
Color.IndianRed,
|
||||
Color.HotPink3,
|
||||
Color.MediumOrchid3,
|
||||
Color.MediumOrchid,
|
||||
Color.MediumPurple2,
|
||||
Color.DarkGoldenrod,
|
||||
Color.LightSalmon3,
|
||||
Color.RosyBrown,
|
||||
Color.Grey63,
|
||||
Color.MediumPurple2_1,
|
||||
Color.MediumPurple1,
|
||||
Color.Gold3,
|
||||
Color.DarkKhaki,
|
||||
Color.NavajoWhite3,
|
||||
Color.Grey69,
|
||||
Color.LightSteelBlue3,
|
||||
Color.LightSteelBlue,
|
||||
Color.Yellow3,
|
||||
Color.DarkOliveGreen3_2,
|
||||
Color.DarkSeaGreen3_1,
|
||||
Color.DarkSeaGreen2,
|
||||
Color.LightCyan3,
|
||||
Color.LightSkyBlue1,
|
||||
Color.GreenYellow,
|
||||
Color.DarkOliveGreen2,
|
||||
Color.PaleGreen1_1,
|
||||
Color.DarkSeaGreen2_1,
|
||||
Color.DarkSeaGreen1,
|
||||
Color.PaleTurquoise1,
|
||||
Color.Red3_1,
|
||||
Color.DeepPink3,
|
||||
Color.DeepPink3_1,
|
||||
Color.Magenta3_1,
|
||||
Color.Magenta3_2,
|
||||
Color.Magenta2,
|
||||
Color.DarkOrange3_1,
|
||||
Color.IndianRed_1,
|
||||
Color.HotPink3_1,
|
||||
Color.HotPink2,
|
||||
Color.Orchid,
|
||||
Color.MediumOrchid1,
|
||||
Color.Orange3,
|
||||
Color.LightSalmon3_1,
|
||||
Color.LightPink3,
|
||||
Color.Pink3,
|
||||
Color.Plum3,
|
||||
Color.Violet,
|
||||
Color.Gold3_1,
|
||||
Color.LightGoldenrod3,
|
||||
Color.Tan,
|
||||
Color.MistyRose3,
|
||||
Color.Thistle3,
|
||||
Color.Plum2,
|
||||
Color.Yellow3_1,
|
||||
Color.Khaki3,
|
||||
Color.LightGoldenrod2,
|
||||
Color.LightYellow3,
|
||||
Color.Grey84,
|
||||
Color.LightSteelBlue1,
|
||||
Color.Yellow2,
|
||||
Color.DarkOliveGreen1,
|
||||
Color.DarkOliveGreen1_1,
|
||||
Color.DarkSeaGreen1_1,
|
||||
Color.Honeydew2,
|
||||
Color.LightCyan1,
|
||||
Color.Red1,
|
||||
Color.DeepPink2,
|
||||
Color.DeepPink1,
|
||||
Color.DeepPink1_1,
|
||||
Color.Magenta2_1,
|
||||
Color.Magenta1,
|
||||
Color.OrangeRed1,
|
||||
Color.IndianRed1,
|
||||
Color.IndianRed1_1,
|
||||
Color.HotPink,
|
||||
Color.HotPink_1,
|
||||
Color.MediumOrchid1_1,
|
||||
Color.DarkOrange,
|
||||
Color.Salmon1,
|
||||
Color.LightCoral,
|
||||
Color.PaleVioletRed1,
|
||||
Color.Orchid2,
|
||||
Color.Orchid1,
|
||||
Color.Orange1,
|
||||
Color.SandyBrown,
|
||||
Color.LightSalmon1,
|
||||
Color.LightPink1,
|
||||
Color.Pink1,
|
||||
Color.Plum1,
|
||||
Color.Gold1,
|
||||
Color.LightGoldenrod2_1,
|
||||
Color.LightGoldenrod2_2,
|
||||
Color.NavajoWhite1,
|
||||
Color.MistyRose1,
|
||||
Color.Thistle1,
|
||||
Color.Yellow1,
|
||||
Color.LightGoldenrod1,
|
||||
Color.Khaki1,
|
||||
Color.Wheat1,
|
||||
Color.Cornsilk1,
|
||||
Color.Grey100,
|
||||
Color.Grey3,
|
||||
Color.Grey7,
|
||||
Color.Grey11,
|
||||
Color.Grey15,
|
||||
Color.Grey19,
|
||||
Color.Grey23,
|
||||
Color.Grey27,
|
||||
Color.Grey30,
|
||||
Color.Grey35,
|
||||
Color.Grey39,
|
||||
Color.Grey42,
|
||||
Color.Grey46,
|
||||
Color.Grey50,
|
||||
Color.Grey54,
|
||||
Color.Grey58,
|
||||
Color.Grey62,
|
||||
Color.Grey66,
|
||||
Color.Grey70,
|
||||
Color.Grey74,
|
||||
Color.Grey78,
|
||||
Color.Grey82,
|
||||
Color.Grey85,
|
||||
Color.Grey89,
|
||||
Color.Grey93,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class ColorPalette
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
public static IReadOnlyList<Color> Legacy { get; }
|
||||
public static IReadOnlyList<Color> Standard { get; }
|
||||
@@ -12,92 +12,15 @@ namespace Spectre.Console.Internal
|
||||
|
||||
static ColorPalette()
|
||||
{
|
||||
Legacy = new List<Color>
|
||||
{
|
||||
Color.Black, Color.Maroon, Color.Green, Color.Olive,
|
||||
Color.Navy, Color.Purple, Color.Teal, Color.Silver,
|
||||
};
|
||||
|
||||
Standard = new List<Color>(Legacy)
|
||||
{
|
||||
Color.Grey, Color.Red, Color.Lime, Color.Yellow,
|
||||
Color.Blue, Color.Fuchsia, Color.Aqua, Color.White,
|
||||
};
|
||||
|
||||
EightBit = new List<Color>(Standard)
|
||||
{
|
||||
Color.Grey0, Color.NavyBlue, Color.DarkBlue, Color.Blue3,
|
||||
Color.Blue3_1, Color.Blue1, Color.DarkGreen, Color.DeepSkyBlue4,
|
||||
Color.DeepSkyBlue4_1, Color.DeepSkyBlue4_2, Color.DodgerBlue3, Color.DodgerBlue2,
|
||||
Color.Green4, Color.SpringGreen4, Color.Turquoise4, Color.DeepSkyBlue3,
|
||||
Color.DeepSkyBlue3_1, Color.DodgerBlue1, Color.Green3, Color.SpringGreen3,
|
||||
Color.DarkCyan, Color.LightSeaGreen, Color.DeepSkyBlue2, Color.DeepSkyBlue1,
|
||||
Color.Green3_1, Color.SpringGreen3_1, Color.SpringGreen2, Color.Cyan3,
|
||||
Color.DarkTurquoise, Color.Turquoise2, Color.Green1, Color.SpringGreen2_1,
|
||||
Color.SpringGreen1, Color.MediumSpringGreen, Color.Cyan2, Color.Cyan1,
|
||||
Color.DarkRed, Color.DeepPink4, Color.Purple4, Color.Purple4_1,
|
||||
Color.Purple3, Color.BlueViolet, Color.Orange4, Color.Grey37,
|
||||
Color.MediumPurple4, Color.SlateBlue3, Color.SlateBlue3_1, Color.RoyalBlue1,
|
||||
Color.Chartreuse4, Color.DarkSeaGreen4, Color.PaleTurquoise4, Color.SteelBlue,
|
||||
Color.SteelBlue3, Color.CornflowerBlue, Color.Chartreuse3, Color.DarkSeaGreen4_1,
|
||||
Color.CadetBlue, Color.CadetBlue_1, Color.SkyBlue3, Color.SteelBlue1,
|
||||
Color.Chartreuse3_1, Color.PaleGreen3, Color.SeaGreen3, Color.Aquamarine3,
|
||||
Color.MediumTurquoise, Color.SteelBlue1_1, Color.Chartreuse2, Color.SeaGreen2,
|
||||
Color.SeaGreen1, Color.SeaGreen1_1, Color.Aquamarine1, Color.DarkSlateGray2,
|
||||
Color.DarkRed_1, Color.DeepPink4_1, Color.DarkMagenta, Color.DarkMagenta_1,
|
||||
Color.DarkViolet, Color.Purple_1, Color.Orange4_1, Color.LightPink4,
|
||||
Color.Plum4, Color.MediumPurple3, Color.MediumPurple3_1, Color.SlateBlue1,
|
||||
Color.Yellow4, Color.Wheat4, Color.Grey53, Color.LightSlateGrey,
|
||||
Color.MediumPurple, Color.LightSlateBlue, Color.Yellow4_1, Color.DarkOliveGreen3,
|
||||
Color.DarkSeaGreen, Color.LightSkyBlue3, Color.LightSkyBlue3_1, Color.SkyBlue2,
|
||||
Color.Chartreuse2_1, Color.DarkOliveGreen3_1, Color.PaleGreen3_1, Color.DarkSeaGreen3,
|
||||
Color.DarkSlateGray3, Color.SkyBlue1, Color.Chartreuse1, Color.LightGreen,
|
||||
Color.LightGreen_1, Color.PaleGreen1, Color.Aquamarine1_1, Color.DarkSlateGray1,
|
||||
Color.Red3, Color.DeepPink4_2, Color.MediumVioletRed, Color.Magenta3,
|
||||
Color.DarkViolet_1, Color.Purple_2, Color.DarkOrange3, Color.IndianRed,
|
||||
Color.HotPink3, Color.MediumOrchid3, Color.MediumOrchid, Color.MediumPurple2,
|
||||
Color.DarkGoldenrod, Color.LightSalmon3, Color.RosyBrown, Color.Grey63,
|
||||
Color.MediumPurple2_1, Color.MediumPurple1, Color.Gold3, Color.DarkKhaki,
|
||||
Color.NavajoWhite3, Color.Grey69, Color.LightSteelBlue3, Color.LightSteelBlue,
|
||||
Color.Yellow3, Color.DarkOliveGreen3_2, Color.DarkSeaGreen3_1, Color.DarkSeaGreen2,
|
||||
Color.LightCyan3, Color.LightSkyBlue1, Color.GreenYellow, Color.DarkOliveGreen2,
|
||||
Color.PaleGreen1_1, Color.DarkSeaGreen2_1, Color.DarkSeaGreen1, Color.PaleTurquoise1,
|
||||
Color.Red3_1, Color.DeepPink3, Color.DeepPink3_1, Color.Magenta3_1,
|
||||
Color.Magenta3_2, Color.Magenta2, Color.DarkOrange3_1, Color.IndianRed_1,
|
||||
Color.HotPink3_1, Color.HotPink2, Color.Orchid, Color.MediumOrchid1,
|
||||
Color.Orange3, Color.LightSalmon3_1, Color.LightPink3, Color.Pink3,
|
||||
Color.Plum3, Color.Violet, Color.Gold3_1, Color.LightGoldenrod3,
|
||||
Color.Tan, Color.MistyRose3, Color.Thistle3, Color.Plum2,
|
||||
Color.Yellow3_1, Color.Khaki3, Color.LightGoldenrod2, Color.LightYellow3,
|
||||
Color.Grey84, Color.LightSteelBlue1, Color.Yellow2, Color.DarkOliveGreen1,
|
||||
Color.DarkOliveGreen1_1, Color.DarkSeaGreen1_1, Color.Honeydew2, Color.LightCyan1,
|
||||
Color.Red1, Color.DeepPink2, Color.DeepPink1, Color.DeepPink1_1,
|
||||
Color.Magenta2_1, Color.Magenta1, Color.OrangeRed1, Color.IndianRed1,
|
||||
Color.IndianRed1_1, Color.HotPink, Color.HotPink_1, Color.MediumOrchid1_1,
|
||||
Color.DarkOrange, Color.Salmon1, Color.LightCoral, Color.PaleVioletRed1,
|
||||
Color.Orchid2, Color.Orchid1, Color.Orange1, Color.SandyBrown,
|
||||
Color.LightSalmon1, Color.LightPink1, Color.Pink1, Color.Plum1,
|
||||
Color.Gold1, Color.LightGoldenrod2_1, Color.LightGoldenrod2_2, Color.NavajoWhite1,
|
||||
Color.MistyRose1, Color.Thistle1, Color.Yellow1, Color.LightGoldenrod1,
|
||||
Color.Khaki1, Color.Wheat1, Color.Cornsilk1, Color.Grey100,
|
||||
Color.Grey3, Color.Grey7, Color.Grey11, Color.Grey15,
|
||||
Color.Grey19, Color.Grey23, Color.Grey27, Color.Grey30,
|
||||
Color.Grey35, Color.Grey39, Color.Grey42, Color.Grey46,
|
||||
Color.Grey50, Color.Grey54, Color.Grey58, Color.Grey62,
|
||||
Color.Grey66, Color.Grey70, Color.Grey74, Color.Grey78,
|
||||
Color.Grey82, Color.Grey85, Color.Grey89, Color.Grey93,
|
||||
};
|
||||
Legacy = GenerateLegacyPalette();
|
||||
Standard = GenerateStandardPalette(Legacy);
|
||||
EightBit = GenerateEightBitPalette(Standard);
|
||||
}
|
||||
|
||||
internal static Color ExactOrClosest(ColorSystem system, Color color)
|
||||
{
|
||||
var exact = Exact(system, color);
|
||||
if (exact != null)
|
||||
{
|
||||
return exact.Value;
|
||||
}
|
||||
|
||||
return Closest(system, color);
|
||||
return exact ?? Closest(system, color);
|
||||
}
|
||||
|
||||
private static Color? Exact(ColorSystem system, Color color)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
if (supportsAnsi)
|
||||
{
|
||||
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)$");
|
||||
var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)\\s*$");
|
||||
var match = regex.Match(RuntimeInformation.OSDescription);
|
||||
if (match.Success && int.TryParse(match.Groups["major"].Value, out var major))
|
||||
{
|
||||
|
||||
281
src/Spectre.Console/Internal/Colors/ColorTable.Generated.cs
Normal file
281
src/Spectre.Console/Internal/Colors/ColorTable.Generated.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Generated 2020-08-03 15:17
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static Dictionary<string, int> GenerateTable()
|
||||
{
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "black", 0 },
|
||||
{ "maroon", 1 },
|
||||
{ "green", 2 },
|
||||
{ "olive", 3 },
|
||||
{ "navy", 4 },
|
||||
{ "purple", 5 },
|
||||
{ "teal", 6 },
|
||||
{ "silver", 7 },
|
||||
{ "grey", 8 },
|
||||
{ "red", 9 },
|
||||
{ "lime", 10 },
|
||||
{ "yellow", 11 },
|
||||
{ "blue", 12 },
|
||||
{ "fuchsia", 13 },
|
||||
{ "aqua", 14 },
|
||||
{ "white", 15 },
|
||||
{ "grey0", 16 },
|
||||
{ "navyblue", 17 },
|
||||
{ "darkblue", 18 },
|
||||
{ "blue3", 19 },
|
||||
{ "blue3_1", 20 },
|
||||
{ "blue1", 21 },
|
||||
{ "darkgreen", 22 },
|
||||
{ "deepskyblue4", 23 },
|
||||
{ "deepskyblue4_1", 24 },
|
||||
{ "deepskyblue4_2", 25 },
|
||||
{ "dodgerblue3", 26 },
|
||||
{ "dodgerblue2", 27 },
|
||||
{ "green4", 28 },
|
||||
{ "springgreen4", 29 },
|
||||
{ "turquoise4", 30 },
|
||||
{ "deepskyblue3", 31 },
|
||||
{ "deepskyblue3_1", 32 },
|
||||
{ "dodgerblue1", 33 },
|
||||
{ "green3", 34 },
|
||||
{ "springgreen3", 35 },
|
||||
{ "darkcyan", 36 },
|
||||
{ "lightseagreen", 37 },
|
||||
{ "deepskyblue2", 38 },
|
||||
{ "deepskyblue1", 39 },
|
||||
{ "green3_1", 40 },
|
||||
{ "springgreen3_1", 41 },
|
||||
{ "springgreen2", 42 },
|
||||
{ "cyan3", 43 },
|
||||
{ "darkturquoise", 44 },
|
||||
{ "turquoise2", 45 },
|
||||
{ "green1", 46 },
|
||||
{ "springgreen2_1", 47 },
|
||||
{ "springgreen1", 48 },
|
||||
{ "mediumspringgreen", 49 },
|
||||
{ "cyan2", 50 },
|
||||
{ "cyan1", 51 },
|
||||
{ "darkred", 52 },
|
||||
{ "deeppink4", 53 },
|
||||
{ "purple4", 54 },
|
||||
{ "purple4_1", 55 },
|
||||
{ "purple3", 56 },
|
||||
{ "blueviolet", 57 },
|
||||
{ "orange4", 58 },
|
||||
{ "grey37", 59 },
|
||||
{ "mediumpurple4", 60 },
|
||||
{ "slateblue3", 61 },
|
||||
{ "slateblue3_1", 62 },
|
||||
{ "royalblue1", 63 },
|
||||
{ "chartreuse4", 64 },
|
||||
{ "darkseagreen4", 65 },
|
||||
{ "paleturquoise4", 66 },
|
||||
{ "steelblue", 67 },
|
||||
{ "steelblue3", 68 },
|
||||
{ "cornflowerblue", 69 },
|
||||
{ "chartreuse3", 70 },
|
||||
{ "darkseagreen4_1", 71 },
|
||||
{ "cadetblue", 72 },
|
||||
{ "cadetblue_1", 73 },
|
||||
{ "skyblue3", 74 },
|
||||
{ "steelblue1", 75 },
|
||||
{ "chartreuse3_1", 76 },
|
||||
{ "palegreen3", 77 },
|
||||
{ "seagreen3", 78 },
|
||||
{ "aquamarine3", 79 },
|
||||
{ "mediumturquoise", 80 },
|
||||
{ "steelblue1_1", 81 },
|
||||
{ "chartreuse2", 82 },
|
||||
{ "seagreen2", 83 },
|
||||
{ "seagreen1", 84 },
|
||||
{ "seagreen1_1", 85 },
|
||||
{ "aquamarine1", 86 },
|
||||
{ "darkslategray2", 87 },
|
||||
{ "darkred_1", 88 },
|
||||
{ "deeppink4_1", 89 },
|
||||
{ "darkmagenta", 90 },
|
||||
{ "darkmagenta_1", 91 },
|
||||
{ "darkviolet", 92 },
|
||||
{ "purple_1", 93 },
|
||||
{ "orange4_1", 94 },
|
||||
{ "lightpink4", 95 },
|
||||
{ "plum4", 96 },
|
||||
{ "mediumpurple3", 97 },
|
||||
{ "mediumpurple3_1", 98 },
|
||||
{ "slateblue1", 99 },
|
||||
{ "yellow4", 100 },
|
||||
{ "wheat4", 101 },
|
||||
{ "grey53", 102 },
|
||||
{ "lightslategrey", 103 },
|
||||
{ "mediumpurple", 104 },
|
||||
{ "lightslateblue", 105 },
|
||||
{ "yellow4_1", 106 },
|
||||
{ "darkolivegreen3", 107 },
|
||||
{ "darkseagreen", 108 },
|
||||
{ "lightskyblue3", 109 },
|
||||
{ "lightskyblue3_1", 110 },
|
||||
{ "skyblue2", 111 },
|
||||
{ "chartreuse2_1", 112 },
|
||||
{ "darkolivegreen3_1", 113 },
|
||||
{ "palegreen3_1", 114 },
|
||||
{ "darkseagreen3", 115 },
|
||||
{ "darkslategray3", 116 },
|
||||
{ "skyblue1", 117 },
|
||||
{ "chartreuse1", 118 },
|
||||
{ "lightgreen", 119 },
|
||||
{ "lightgreen_1", 120 },
|
||||
{ "palegreen1", 121 },
|
||||
{ "aquamarine1_1", 122 },
|
||||
{ "darkslategray1", 123 },
|
||||
{ "red3", 124 },
|
||||
{ "deeppink4_2", 125 },
|
||||
{ "mediumvioletred", 126 },
|
||||
{ "magenta3", 127 },
|
||||
{ "darkviolet_1", 128 },
|
||||
{ "purple_2", 129 },
|
||||
{ "darkorange3", 130 },
|
||||
{ "indianred", 131 },
|
||||
{ "hotpink3", 132 },
|
||||
{ "mediumorchid3", 133 },
|
||||
{ "mediumorchid", 134 },
|
||||
{ "mediumpurple2", 135 },
|
||||
{ "darkgoldenrod", 136 },
|
||||
{ "lightsalmon3", 137 },
|
||||
{ "rosybrown", 138 },
|
||||
{ "grey63", 139 },
|
||||
{ "mediumpurple2_1", 140 },
|
||||
{ "mediumpurple1", 141 },
|
||||
{ "gold3", 142 },
|
||||
{ "darkkhaki", 143 },
|
||||
{ "navajowhite3", 144 },
|
||||
{ "grey69", 145 },
|
||||
{ "lightsteelblue3", 146 },
|
||||
{ "lightsteelblue", 147 },
|
||||
{ "yellow3", 148 },
|
||||
{ "darkolivegreen3_2", 149 },
|
||||
{ "darkseagreen3_1", 150 },
|
||||
{ "darkseagreen2", 151 },
|
||||
{ "lightcyan3", 152 },
|
||||
{ "lightskyblue1", 153 },
|
||||
{ "greenyellow", 154 },
|
||||
{ "darkolivegreen2", 155 },
|
||||
{ "palegreen1_1", 156 },
|
||||
{ "darkseagreen2_1", 157 },
|
||||
{ "darkseagreen1", 158 },
|
||||
{ "paleturquoise1", 159 },
|
||||
{ "red3_1", 160 },
|
||||
{ "deeppink3", 161 },
|
||||
{ "deeppink3_1", 162 },
|
||||
{ "magenta3_1", 163 },
|
||||
{ "magenta3_2", 164 },
|
||||
{ "magenta2", 165 },
|
||||
{ "darkorange3_1", 166 },
|
||||
{ "indianred_1", 167 },
|
||||
{ "hotpink3_1", 168 },
|
||||
{ "hotpink2", 169 },
|
||||
{ "orchid", 170 },
|
||||
{ "mediumorchid1", 171 },
|
||||
{ "orange3", 172 },
|
||||
{ "lightsalmon3_1", 173 },
|
||||
{ "lightpink3", 174 },
|
||||
{ "pink3", 175 },
|
||||
{ "plum3", 176 },
|
||||
{ "violet", 177 },
|
||||
{ "gold3_1", 178 },
|
||||
{ "lightgoldenrod3", 179 },
|
||||
{ "tan", 180 },
|
||||
{ "mistyrose3", 181 },
|
||||
{ "thistle3", 182 },
|
||||
{ "plum2", 183 },
|
||||
{ "yellow3_1", 184 },
|
||||
{ "khaki3", 185 },
|
||||
{ "lightgoldenrod2", 186 },
|
||||
{ "lightyellow3", 187 },
|
||||
{ "grey84", 188 },
|
||||
{ "lightsteelblue1", 189 },
|
||||
{ "yellow2", 190 },
|
||||
{ "darkolivegreen1", 191 },
|
||||
{ "darkolivegreen1_1", 192 },
|
||||
{ "darkseagreen1_1", 193 },
|
||||
{ "honeydew2", 194 },
|
||||
{ "lightcyan1", 195 },
|
||||
{ "red1", 196 },
|
||||
{ "deeppink2", 197 },
|
||||
{ "deeppink1", 198 },
|
||||
{ "deeppink1_1", 199 },
|
||||
{ "magenta2_1", 200 },
|
||||
{ "magenta1", 201 },
|
||||
{ "orangered1", 202 },
|
||||
{ "indianred1", 203 },
|
||||
{ "indianred1_1", 204 },
|
||||
{ "hotpink", 205 },
|
||||
{ "hotpink_1", 206 },
|
||||
{ "mediumorchid1_1", 207 },
|
||||
{ "darkorange", 208 },
|
||||
{ "salmon1", 209 },
|
||||
{ "lightcoral", 210 },
|
||||
{ "palevioletred1", 211 },
|
||||
{ "orchid2", 212 },
|
||||
{ "orchid1", 213 },
|
||||
{ "orange1", 214 },
|
||||
{ "sandybrown", 215 },
|
||||
{ "lightsalmon1", 216 },
|
||||
{ "lightpink1", 217 },
|
||||
{ "pink1", 218 },
|
||||
{ "plum1", 219 },
|
||||
{ "gold1", 220 },
|
||||
{ "lightgoldenrod2_1", 221 },
|
||||
{ "lightgoldenrod2_2", 222 },
|
||||
{ "navajowhite1", 223 },
|
||||
{ "mistyrose1", 224 },
|
||||
{ "thistle1", 225 },
|
||||
{ "yellow1", 226 },
|
||||
{ "lightgoldenrod1", 227 },
|
||||
{ "khaki1", 228 },
|
||||
{ "wheat1", 229 },
|
||||
{ "cornsilk1", 230 },
|
||||
{ "grey100", 231 },
|
||||
{ "grey3", 232 },
|
||||
{ "grey7", 233 },
|
||||
{ "grey11", 234 },
|
||||
{ "grey15", 235 },
|
||||
{ "grey19", 236 },
|
||||
{ "grey23", 237 },
|
||||
{ "grey27", 238 },
|
||||
{ "grey30", 239 },
|
||||
{ "grey35", 240 },
|
||||
{ "grey39", 241 },
|
||||
{ "grey42", 242 },
|
||||
{ "grey46", 243 },
|
||||
{ "grey50", 244 },
|
||||
{ "grey54", 245 },
|
||||
{ "grey58", 246 },
|
||||
{ "grey62", 247 },
|
||||
{ "grey66", 248 },
|
||||
{ "grey70", 249 },
|
||||
{ "grey74", 250 },
|
||||
{ "grey78", 251 },
|
||||
{ "grey82", 252 },
|
||||
{ "grey85", 253 },
|
||||
{ "grey89", 254 },
|
||||
{ "grey93", 255 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/Spectre.Console/Internal/Colors/ColorTable.cs
Normal file
54
src/Spectre.Console/Internal/Colors/ColorTable.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static readonly Dictionary<int, string> _nameLookup;
|
||||
private static readonly Dictionary<string, int> _numberLookup;
|
||||
|
||||
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
|
||||
static ColorTable()
|
||||
{
|
||||
_numberLookup = GenerateTable();
|
||||
_nameLookup = new Dictionary<int, string>();
|
||||
foreach (var pair in _numberLookup)
|
||||
{
|
||||
_nameLookup.Add(pair.Value, pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public static Color GetColor(int number)
|
||||
{
|
||||
if (number < 0 || number > 255)
|
||||
{
|
||||
throw new InvalidOperationException("Color number must be between 0 and 255");
|
||||
}
|
||||
|
||||
return ColorPalette.EightBit[number];
|
||||
}
|
||||
|
||||
public static Color? GetColor(string name)
|
||||
{
|
||||
if (!_numberLookup.TryGetValue(name, out var number))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (number > ColorPalette.EightBit.Count - 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ColorPalette.EightBit[number];
|
||||
}
|
||||
|
||||
public static string? GetName(int number)
|
||||
{
|
||||
_nameLookup.TryGetValue(number, out var name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
@@ -13,9 +14,41 @@ namespace Spectre.Console.Internal
|
||||
|
||||
var buffer = settings.Out ?? System.Console.Out;
|
||||
|
||||
var supportsAnsi = settings.Ansi == AnsiSupport.Detect
|
||||
? AnsiDetector.SupportsAnsi(true)
|
||||
: settings.Ansi == AnsiSupport.Yes;
|
||||
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
|
||||
var legacyConsole = false;
|
||||
|
||||
if (settings.Ansi == AnsiSupport.Detect)
|
||||
{
|
||||
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(true);
|
||||
|
||||
// Check whether or not this is a legacy console from the existing instance (if any).
|
||||
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
|
||||
if (AnsiConsole.Created && !legacyConsole && buffer.IsStandardOut() && AnsiConsole.Capabilities.LegacyConsole)
|
||||
{
|
||||
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffer.IsStandardOut())
|
||||
{
|
||||
// Are we running on Windows?
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Not the first console we're creating?
|
||||
if (AnsiConsole.Created)
|
||||
{
|
||||
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try detecting whether or not this
|
||||
(_, legacyConsole) = AnsiDetector.Detect(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
|
||||
? ColorSystemDetector.Detect(supportsAnsi)
|
||||
@@ -23,13 +56,13 @@ namespace Spectre.Console.Internal
|
||||
|
||||
if (supportsAnsi)
|
||||
{
|
||||
return new AnsiConsoleRenderer(buffer, colorSystem)
|
||||
return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole)
|
||||
{
|
||||
Style = Styles.None,
|
||||
Decoration = Decoration.None,
|
||||
};
|
||||
}
|
||||
|
||||
return new FallbackConsoleRenderer(buffer, colorSystem);
|
||||
return new FallbackConsoleRenderer(buffer, colorSystem, legacyConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
src/Spectre.Console/Internal/DecorationTable.cs
Normal file
39
src/Spectre.Console/Internal/DecorationTable.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
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 },
|
||||
{ "b", Decoration.Bold },
|
||||
{ "dim", Decoration.Dim },
|
||||
{ "italic", Decoration.Italic },
|
||||
{ "i", Decoration.Italic },
|
||||
{ "underline", Decoration.Underline },
|
||||
{ "u", Decoration.Underline },
|
||||
{ "invert", Decoration.Invert },
|
||||
{ "conceal", Decoration.Conceal },
|
||||
{ "slowblink", Decoration.SlowBlink },
|
||||
{ "rapidblink", Decoration.RapidBlink },
|
||||
{ "strikethrough", Decoration.Strikethrough },
|
||||
{ "s", Decoration.Strikethrough },
|
||||
};
|
||||
}
|
||||
|
||||
public static Decoration? GetDecoration(string name)
|
||||
{
|
||||
_lookup.TryGetValue(name, out var result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,25 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class ConsoleExtensions
|
||||
{
|
||||
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
if (style is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(style));
|
||||
}
|
||||
|
||||
var current = new Style(console.Foreground, console.Background, console.Decoration);
|
||||
console.SetColor(style.Foreground, true);
|
||||
console.SetColor(style.Background, false);
|
||||
console.Decoration = style.Decoration;
|
||||
return new StyleScope(console, current);
|
||||
}
|
||||
|
||||
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
|
||||
{
|
||||
if (console is null)
|
||||
@@ -17,16 +36,16 @@ namespace Spectre.Console.Internal
|
||||
return new ColorScope(console, current, foreground);
|
||||
}
|
||||
|
||||
public static IDisposable PushStyle(this IAnsiConsole console, Styles style)
|
||||
public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration)
|
||||
{
|
||||
if (console is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
var current = console.Style;
|
||||
console.Style = style;
|
||||
return new StyleScope(console, current);
|
||||
var current = console.Decoration;
|
||||
console.Decoration = decoration;
|
||||
return new DecorationScope(console, current);
|
||||
}
|
||||
|
||||
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
|
||||
@@ -47,6 +66,33 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StyleScope : IDisposable
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly Style _style;
|
||||
|
||||
public StyleScope(IAnsiConsole console, Style style)
|
||||
{
|
||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
_style = style ?? throw new ArgumentNullException(nameof(style));
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
|
||||
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
|
||||
~StyleScope()
|
||||
{
|
||||
throw new InvalidOperationException("Style scope was not disposed.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_console.SetColor(_style.Foreground, true);
|
||||
_console.SetColor(_style.Background, false);
|
||||
_console.Decoration = _style.Decoration;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ColorScope : IDisposable
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
@@ -74,28 +120,28 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StyleScope : IDisposable
|
||||
internal sealed class DecorationScope : IDisposable
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly Styles _style;
|
||||
private readonly Decoration _decoration;
|
||||
|
||||
public StyleScope(IAnsiConsole console, Styles color)
|
||||
public DecorationScope(IAnsiConsole console, Decoration decoration)
|
||||
{
|
||||
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
_style = color;
|
||||
_decoration = decoration;
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
|
||||
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
|
||||
~StyleScope()
|
||||
~DecorationScope()
|
||||
{
|
||||
throw new InvalidOperationException("Style scope was not disposed.");
|
||||
throw new InvalidOperationException("Decoration scope was not disposed.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_console.Style = _style;
|
||||
_console.Decoration = _decoration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user