Compare commits

...

41 Commits

Author SHA1 Message Date
Patrik Svensson
8a01b93aca Don't limit tables and grids to markup text
Closes #13
2020-08-24 23:28:40 +02:00
Patrik Svensson
effdecb1d4 Add GitHub Sponsors data (skip-ci) 2020-08-24 12:04:01 +02:00
Patrik Svensson
4cfe55cc27 Emit native line breaks 2020-08-16 13:47:57 +02:00
Patrik Svensson
5b33f80213 Fix line ending problem with text 2020-08-16 12:26:51 +02:00
Patrik Svensson
d7bbaf4a85 Add word wrapping for text
Closes #18
2020-08-14 18:19:24 +02:00
Patrik Svensson
0119364728 Add examples of how to use Spectre.Console 2020-08-12 14:59:17 +02:00
Patrik Svensson
1d74fb909c Add support for adding empty rows
This affects grids and tables.
2020-08-11 17:49:28 +02:00
Patrik Svensson
5d132220ba Enable nullable reference types
Closes #36
2020-08-11 17:24:34 +02:00
Patrik Svensson
a273f74758 Add aliases for styles
Closes #37
2020-08-11 16:45:05 +02:00
Patrik Svensson
717931f11c Add support for RGB colors
Closes #34
2020-08-11 16:45:05 +02:00
Patrik Svensson
bcfc495843 Add support for hex colors
Closes #33
2020-08-11 16:45:05 +02:00
Patrik Svensson
9aa36c4cf0 Do not include cell separators in grid
Closes #40
2020-08-11 11:53:34 +02:00
Patrik Svensson
22d4af4482 Preserve line breaks 2020-08-10 11:42:34 +02:00
Patrik Svensson
f4d1796e40 Do not split lines if width is 0
Closes #32
2020-08-09 11:53:56 +02:00
Patrik Svensson
2dd0eb9f74 Add support for column alignment and padding
Closes #12
Closes #31
2020-08-08 23:11:34 +02:00
Patrik Svensson
fa85216554 Add fallback for unicode borders 2020-08-07 22:24:38 +02:00
Patrik Svensson
d475e3b30a Reset colors before line break
Closes #28
2020-08-07 13:12:03 +02:00
Patrik Svensson
9637066927 Add better algorithm for calculating column widths
Closes #14
2020-08-07 12:55:33 +02:00
AdmiringWorm
0b4321115a Add background color examples in Sample console 2020-08-07 08:20:20 +02:00
AdmiringWorm
5cd9ece31a Add check for IsDefault when comparing Colors 2020-08-07 08:20:20 +02:00
AdmiringWorm
b0341862cf Add failing unit test for comparing black and default color 2020-08-07 08:20:20 +02:00
AdmiringWorm
2e7b3d520a Update regex to correctly identify Windows 10
When running .NET Core 2.1 or .NET Framework 4.6.1, the used regex for
detecting Windows 10 TrueColor support does not match the OSDescription.
The reason for this is because on those frameworks the description has
trailing whitespaces, but they do not on .NET Core 3.1.
This commit updates the regex to ignore any trailing whitespaces.
2020-08-07 08:11:34 +02:00
Patrik Svensson
646f51a628 Fix NuGet badge (skip-ci) 2020-08-05 16:03:09 +02:00
Patrik Svensson
a0bd481255 Add package icon
Closes #20
2020-08-05 15:49:43 +02:00
Patrik Svensson
6d197c5140 Add border support for panels
Closes #11
2020-08-05 15:28:15 +02:00
Patrik Svensson
108e56c229 Add rounded border 2020-08-05 14:19:45 +02:00
Patrik Svensson
66994cd904 Add grid support 2020-08-05 00:44:05 +02:00
Patrik Svensson
f9bd936254 Add support for showing no border 2020-08-05 00:44:05 +02:00
Patrik Svensson
a068fc68c3 Add support for tables 2020-08-04 22:24:13 +02:00
Patrik Svensson
aa34c145b9 skip-ci: Do not explicitly fetch tags 2020-08-04 09:35:29 +02:00
Patrik Svensson
98cf63f485 Rename Style and Appearance
* Renames Style -> Decoration
* Renames Appearance -> Style
* Adds Style.Parse and Style.TryParse
2020-08-03 23:30:47 +02:00
Patrik Svensson
c3286a4842 Rename folder 2020-08-03 15:34:33 +02:00
Patrik Svensson
e5bf2bd498 Autogenerate known colors and palettes
This will make it a bit easier to make changes
2020-08-03 15:22:39 +02:00
Patrik Svensson
5267ebda49 Get color names from lookup table
Also adds tests for Color struct and fixes a bug
that had to do with equality.
2020-08-03 11:32:17 +02:00
Patrik Svensson
f19202b427 Improve text composite
- A `Text` object should not be able to justify itself.
  All justification needs to be done by a parent.
- Apply colors and styles to part of a `Text` object
- Markup parser should return a `Text` object
2020-08-02 22:45:01 +02:00
Patrik Svensson
8e4f33bba4 Added initial support for rendering composites
This is far from complete, but it's a start
and it will enable us to create things like tables
and other complex objects in the long run.
2020-07-30 22:55:42 +02:00
Patrik Svensson
e596e6eb4f Add example showing how to use System.ConsoleColor 2020-07-24 22:52:25 +02:00
Patrik Svensson
a80120babc skip-ci: Fix typo in README 2020-07-24 22:47:18 +02:00
Patrik Svensson
214d510d6d skip-ci: Add System.ConsoleColor info to README 2020-07-24 22:46:17 +02:00
Patrik Svensson
0986a5f744 Add parser and renderer for markup language 2020-07-24 22:37:33 +02:00
Patrik Svensson
b72a695c35 Remove <public> keyword from IAnsiConsole 2020-07-23 18:33:07 +02:00
129 changed files with 11627 additions and 960 deletions

1
.github/funding.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: patriksvensson

View File

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

View File

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

333
README.md
View File

@@ -1,11 +1,22 @@
# `Spectre.Console`
_[![Spectre.IO NuGet Version](https://img.shields.io/nuget/v/spectre.io.svg?style=flat&label=NuGet%3A%20Spectre.Console)](https://www.nuget.org/packages/spectre.console)_
_[![Spectre.Console NuGet Version](https://img.shields.io/nuget/v/spectre.console.svg?style=flat&label=NuGet%3A%20Spectre.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)
for Python.
## Table of Contents
1. [Features](#features)
2. [Example](#example)
3. [Usage](#usage)
3.1. [Using the static API](#using-the-static-api)
3.2. [Creating a console](#creating-a-console)
4. [Running examples](#running-examples)
5. [Available styles](#available-styles)
6. [Predefined colors](#predefined-colors)
## Features
* Written with unit testing in mind.
@@ -40,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.WriteLine("Good bye!");
AnsiConsole.MarkupLine("[bold yellow on red]{0}[/] [underline]world[/]!", "Goodbye");
```
If you want to get a reference to the default `IAnsiConsole`,
@@ -56,6 +67,9 @@ Sometimes it's useful to explicitly create a console with specific
capabilities, such as during unit testing when you want control
over the environment your code runs in.
It's recommended to not use `AnsiConsole` in code that run as
part of a unit test.
```csharp
IAnsiConsole console = AnsiConsole.Create(
new AnsiConsoleSettings()
@@ -70,3 +84,316 @@ _NOTE: Even if you can specify a specific color system to use
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._
Name | Description
--- | ---
`bold` | Bold text
`dim` | Dim or faint text
`italic` | Italic text
`underline` | Underlined text
`invert` | Swaps the foreground and background colors
`conceal` | Hides the text
`slowblink` | Makes text blink slowly
`rapidblink` | Makes text blink
`strikethrough` | Shows text with a horizontal line through the center
## Predefined colors
Number | Name | RGB | Hex | System.ConsoleColor
--- | --- | --- | --- | ---
`0` | `black` | `0,0,0` | `#000000` | `Black`
`1` | `maroon` | `128,0,0` | `#800000` | `DarkRed`
`2` | `green` | `0,128,0` | `#008000` | `DarkGreen`
`3` | `olive` | `128,128,0` | `#808000` | `DarkYellow`
`4` | `navy` | `0,0,128` | `#000080` | `DarkBlue`
`5` | `purple` | `128,0,128` | `#800080` | `DarkMagenta`
`6` | `teal` | `0,128,128` | `#008080` | `DarkCyan`
`7` | `silver` | `192,192,192` | `#c0c0c0` | `Gray`
`8` | `grey` | `128,128,128` | `#808080` | `DarkGray`
`9` | `red` | `255,0,0` | `#ff0000` | `Red`
`10` | `lime` | `0,255,0` | `#00ff00` | `Green`
`11` | `yellow` | `255,255,0` | `#ffff00` | `Yellow`
`12` | `blue` | `0,0,255` | `#0000ff` | `Blue`
`13` | `fuchsia` | `255,0,255` | `#ff00ff` | `Magenta`
`14` | `aqua` | `0,255,255` | `#00ffff` | `Cyan`
`15` | `white` | `255,255,255` | `#ffffff` | `White`
`16` | `grey0` | `0,0,0` | `#000000`
`17` | `navyblue` | `0,0,95` | `#00005f`
`18` | `darkblue` | `0,0,135` | `#000087`
`19` | `blue3` | `0,0,175` | `#0000af`
`20` | `blue3_1` | `0,0,215` | `#0000d7`
`21` | `blue1` | `0,0,255` | `#0000ff`
`22` | `darkgreen` | `0,95,0` | `#005f00`
`23` | `deepskyblue4` | `0,95,95` | `#005f5f`
`24` | `deepskyblue4_1` | `0,95,135` | `#005f87`
`25` | `deepskyblue4_2` | `0,95,175` | `#005faf`
`26` | `dodgerblue3` | `0,95,215` | `#005fd7`
`27` | `dodgerblue2` | `0,95,255` | `#005fff`
`28` | `green4` | `0,135,0` | `#008700`
`29` | `springgreen4` | `0,135,95` | `#00875f`
`30` | `turquoise4` | `0,135,135` | `#008787`
`31` | `deepskyblue3` | `0,135,175` | `#0087af`
`32` | `deepskyblue3_1` | `0,135,215` | `#0087d7`
`33` | `dodgerblue1` | `0,135,255` | `#0087ff`
`34` | `green3` | `0,175,0` | `#00af00`
`35` | `springgreen3` | `0,175,95` | `#00af5f`
`36` | `darkcyan` | `0,175,135` | `#00af87`
`37` | `lightseagreen` | `0,175,175` | `#00afaf`
`38` | `deepskyblue2` | `0,175,215` | `#00afd7`
`39` | `deepskyblue1` | `0,175,255` | `#00afff`
`40` | `green3_1` | `0,215,0` | `#00d700`
`41` | `springgreen3_1` | `0,215,95` | `#00d75f`
`42` | `springgreen2` | `0,215,135` | `#00d787`
`43` | `cyan3` | `0,215,175` | `#00d7af`
`44` | `darkturquoise` | `0,215,215` | `#00d7d7`
`45` | `turquoise2` | `0,215,255` | `#00d7ff`
`46` | `green1` | `0,255,0` | `#00ff00`
`47` | `springgreen2_1` | `0,255,95` | `#00ff5f`
`48` | `springgreen1` | `0,255,135` | `#00ff87`
`49` | `mediumspringgreen` | `0,255,175` | `#00ffaf`
`50` | `cyan2` | `0,255,215` | `#00ffd7`
`51` | `cyan1` | `0,255,255` | `#00ffff`
`52` | `darkred` | `95,0,0` | `#5f0000`
`53` | `deeppink4` | `95,0,95` | `#5f005f`
`54` | `purple4` | `95,0,135` | `#5f0087`
`55` | `purple4_1` | `95,0,175` | `#5f00af`
`56` | `purple3` | `95,0,215` | `#5f00d7`
`57` | `blueviolet` | `95,0,255` | `#5f00ff`
`58` | `orange4` | `95,95,0` | `#5f5f00`
`59` | `grey37` | `95,95,95` | `#5f5f5f`
`60` | `mediumpurple4` | `95,95,135` | `#5f5f87`
`61` | `slateblue3` | `95,95,175` | `#5f5faf`
`62` | `slateblue3_1` | `95,95,215` | `#5f5fd7`
`63` | `royalblue1` | `95,95,255` | `#5f5fff`
`64` | `chartreuse4` | `95,135,0` | `#5f8700`
`65` | `darkseagreen4` | `95,135,95` | `#5f875f`
`66` | `paleturquoise4` | `95,135,135` | `#5f8787`
`67` | `steelblue` | `95,135,175` | `#5f87af`
`68` | `steelblue3` | `95,135,215` | `#5f87d7`
`69` | `cornflowerblue` | `95,135,255` | `#5f87ff`
`70` | `chartreuse3` | `95,175,0` | `#5faf00`
`71` | `darkseagreen4_1` | `95,175,95` | `#5faf5f`
`72` | `cadetblue` | `95,175,135` | `#5faf87`
`73` | `cadetblue_1` | `95,175,175` | `#5fafaf`
`74` | `skyblue3` | `95,175,215` | `#5fafd7`
`75` | `steelblue1` | `95,175,255` | `#5fafff`
`76` | `chartreuse3_1` | `95,215,0` | `#5fd700`
`77` | `palegreen3` | `95,215,95` | `#5fd75f`
`78` | `seagreen3` | `95,215,135` | `#5fd787`
`79` | `aquamarine3` | `95,215,175` | `#5fd7af`
`80` | `mediumturquoise` | `95,215,215` | `#5fd7d7`
`81` | `steelblue1_1` | `95,215,255` | `#5fd7ff`
`82` | `chartreuse2` | `95,255,0` | `#5fff00`
`83` | `seagreen2` | `95,255,95` | `#5fff5f`
`84` | `seagreen1` | `95,255,135` | `#5fff87`
`85` | `seagreen1_1` | `95,255,175` | `#5fffaf`
`86` | `aquamarine1` | `95,255,215` | `#5fffd7`
`87` | `darkslategray2` | `95,255,255` | `#5fffff`
`88` | `darkred_1` | `135,0,0` | `#870000`
`89` | `deeppink4_1` | `135,0,95` | `#87005f`
`90` | `darkmagenta` | `135,0,135` | `#870087`
`91` | `darkmagenta_1` | `135,0,175` | `#8700af`
`92` | `darkviolet` | `135,0,215` | `#8700d7`
`93` | `purple_1` | `135,0,255` | `#8700ff`
`94` | `orange4_1` | `135,95,0` | `#875f00`
`95` | `lightpink4` | `135,95,95` | `#875f5f`
`96` | `plum4` | `135,95,135` | `#875f87`
`97` | `mediumpurple3` | `135,95,175` | `#875faf`
`98` | `mediumpurple3_1` | `135,95,215` | `#875fd7`
`99` | `slateblue1` | `135,95,255` | `#875fff`
`100` | `yellow4` | `135,135,0` | `#878700`
`101` | `wheat4` | `135,135,95` | `#87875f`
`102` | `grey53` | `135,135,135` | `#878787`
`103` | `lightslategrey` | `135,135,175` | `#8787af`
`104` | `mediumpurple` | `135,135,215` | `#8787d7`
`105` | `lightslateblue` | `135,135,255` | `#8787ff`
`106` | `yellow4_1` | `135,175,0` | `#87af00`
`107` | `darkolivegreen3` | `135,175,95` | `#87af5f`
`108` | `darkseagreen` | `135,175,135` | `#87af87`
`109` | `lightskyblue3` | `135,175,175` | `#87afaf`
`110` | `lightskyblue3_1` | `135,175,215` | `#87afd7`
`111` | `skyblue2` | `135,175,255` | `#87afff`
`112` | `chartreuse2_1` | `135,215,0` | `#87d700`
`113` | `darkolivegreen3_1` | `135,215,95` | `#87d75f`
`114` | `palegreen3_1` | `135,215,135` | `#87d787`
`115` | `darkseagreen3` | `135,215,175` | `#87d7af`
`116` | `darkslategray3` | `135,215,215` | `#87d7d7`
`117` | `skyblue1` | `135,215,255` | `#87d7ff`
`118` | `chartreuse1` | `135,255,0` | `#87ff00`
`119` | `lightgreen` | `135,255,95` | `#87ff5f`
`120` | `lightgreen_1` | `135,255,135` | `#87ff87`
`121` | `palegreen1` | `135,255,175` | `#87ffaf`
`122` | `aquamarine1_1` | `135,255,215` | `#87ffd7`
`123` | `darkslategray1` | `135,255,255` | `#87ffff`
`124` | `red3` | `175,0,0` | `#af0000`
`125` | `deeppink4_2` | `175,0,95` | `#af005f`
`126` | `mediumvioletred` | `175,0,135` | `#af0087`
`127` | `magenta3` | `175,0,175` | `#af00af`
`128` | `darkviolet_1` | `175,0,215` | `#af00d7`
`129` | `purple_2` | `175,0,255` | `#af00ff`
`130` | `darkorange3` | `175,95,0` | `#af5f00`
`131` | `indianred` | `175,95,95` | `#af5f5f`
`132` | `hotpink3` | `175,95,135` | `#af5f87`
`133` | `mediumorchid3` | `175,95,175` | `#af5faf`
`134` | `mediumorchid` | `175,95,215` | `#af5fd7`
`135` | `mediumpurple2` | `175,95,255` | `#af5fff`
`136` | `darkgoldenrod` | `175,135,0` | `#af8700`
`137` | `lightsalmon3` | `175,135,95` | `#af875f`
`138` | `rosybrown` | `175,135,135` | `#af8787`
`139` | `grey63` | `175,135,175` | `#af87af`
`140` | `mediumpurple2_1` | `175,135,215` | `#af87d7`
`141` | `mediumpurple1` | `175,135,255` | `#af87ff`
`142` | `gold3` | `175,175,0` | `#afaf00`
`143` | `darkkhaki` | `175,175,95` | `#afaf5f`
`144` | `navajowhite3` | `175,175,135` | `#afaf87`
`145` | `grey69` | `175,175,175` | `#afafaf`
`146` | `lightsteelblue3` | `175,175,215` | `#afafd7`
`147` | `lightsteelblue` | `175,175,255` | `#afafff`
`148` | `yellow3` | `175,215,0` | `#afd700`
`149` | `darkolivegreen3_2` | `175,215,95` | `#afd75f`
`150` | `darkseagreen3_1` | `175,215,135` | `#afd787`
`151` | `darkseagreen2` | `175,215,175` | `#afd7af`
`152` | `lightcyan3` | `175,215,215` | `#afd7d7`
`153` | `lightskyblue1` | `175,215,255` | `#afd7ff`
`154` | `greenyellow` | `175,255,0` | `#afff00`
`155` | `darkolivegreen2` | `175,255,95` | `#afff5f`
`156` | `palegreen1_1` | `175,255,135` | `#afff87`
`157` | `darkseagreen2_1` | `175,255,175` | `#afffaf`
`158` | `darkseagreen1` | `175,255,215` | `#afffd7`
`159` | `paleturquoise1` | `175,255,255` | `#afffff`
`160` | `red3_1` | `215,0,0` | `#d70000`
`161` | `deeppink3` | `215,0,95` | `#d7005f`
`162` | `deeppink3_1` | `215,0,135` | `#d70087`
`163` | `magenta3_1` | `215,0,175` | `#d700af`
`164` | `magenta3_2` | `215,0,215` | `#d700d7`
`165` | `magenta2` | `215,0,255` | `#d700ff`
`166` | `darkorange3_1` | `215,95,0` | `#d75f00`
`167` | `indianred_1` | `215,95,95` | `#d75f5f`
`168` | `hotpink3_1` | `215,95,135` | `#d75f87`
`169` | `hotpink2` | `215,95,175` | `#d75faf`
`170` | `orchid` | `215,95,215` | `#d75fd7`
`171` | `mediumorchid1` | `215,95,255` | `#d75fff`
`172` | `orange3` | `215,135,0` | `#d78700`
`173` | `lightsalmon3_1` | `215,135,95` | `#d7875f`
`174` | `lightpink3` | `215,135,135` | `#d78787`
`175` | `pink3` | `215,135,175` | `#d787af`
`176` | `plum3` | `215,135,215` | `#d787d7`
`177` | `violet` | `215,135,255` | `#d787ff`
`178` | `gold3_1` | `215,175,0` | `#d7af00`
`179` | `lightgoldenrod3` | `215,175,95` | `#d7af5f`
`180` | `tan` | `215,175,135` | `#d7af87`
`181` | `mistyrose3` | `215,175,175` | `#d7afaf`
`182` | `thistle3` | `215,175,215` | `#d7afd7`
`183` | `plum2` | `215,175,255` | `#d7afff`
`184` | `yellow3_1` | `215,215,0` | `#d7d700`
`185` | `khaki3` | `215,215,95` | `#d7d75f`
`186` | `lightgoldenrod2` | `215,215,135` | `#d7d787`
`187` | `lightyellow3` | `215,215,175` | `#d7d7af`
`188` | `grey84` | `215,215,215` | `#d7d7d7`
`189` | `lightsteelblue1` | `215,215,255` | `#d7d7ff`
`190` | `yellow2` | `215,255,0` | `#d7ff00`
`191` | `darkolivegreen1` | `215,255,95` | `#d7ff5f`
`192` | `darkolivegreen1_1` | `215,255,135` | `#d7ff87`
`193` | `darkseagreen1_1` | `215,255,175` | `#d7ffaf`
`194` | `honeydew2` | `215,255,215` | `#d7ffd7`
`195` | `lightcyan1` | `215,255,255` | `#d7ffff`
`196` | `red1` | `255,0,0` | `#ff0000`
`197` | `deeppink2` | `255,0,95` | `#ff005f`
`198` | `deeppink1` | `255,0,135` | `#ff0087`
`199` | `deeppink1_1` | `255,0,175` | `#ff00af`
`200` | `magenta2_1` | `255,0,215` | `#ff00d7`
`201` | `magenta1` | `255,0,255` | `#ff00ff`
`202` | `orangered1` | `255,95,0` | `#ff5f00`
`203` | `indianred1` | `255,95,95` | `#ff5f5f`
`204` | `indianred1_1` | `255,95,135` | `#ff5f87`
`205` | `hotpink` | `255,95,175` | `#ff5faf`
`206` | `hotpink_1` | `255,95,215` | `#ff5fd7`
`207` | `mediumorchid1_1` | `255,95,255` | `#ff5fff`
`208` | `darkorange` | `255,135,0` | `#ff8700`
`209` | `salmon1` | `255,135,95` | `#ff875f`
`210` | `lightcoral` | `255,135,135` | `#ff8787`
`211` | `palevioletred1` | `255,135,175` | `#ff87af`
`212` | `orchid2` | `255,135,215` | `#ff87d7`
`213` | `orchid1` | `255,135,255` | `#ff87ff`
`214` | `orange1` | `255,175,0` | `#ffaf00`
`215` | `sandybrown` | `255,175,95` | `#ffaf5f`
`216` | `lightsalmon1` | `255,175,135` | `#ffaf87`
`217` | `lightpink1` | `255,175,175` | `#ffafaf`
`218` | `pink1` | `255,175,215` | `#ffafd7`
`219` | `plum1` | `255,175,255` | `#ffafff`
`220` | `gold1` | `255,215,0` | `#ffd700`
`221` | `lightgoldenrod2_1` | `255,215,95` | `#ffd75f`
`222` | `lightgoldenrod2_2` | `255,215,135` | `#ffd787`
`223` | `navajowhite1` | `255,215,175` | `#ffd7af`
`224` | `mistyrose1` | `255,215,215` | `#ffd7d7`
`225` | `thistle1` | `255,215,255` | `#ffd7ff`
`226` | `yellow1` | `255,255,0` | `#ffff00`
`227` | `lightgoldenrod1` | `255,255,95` | `#ffff5f`
`228` | `khaki1` | `255,255,135` | `#ffff87`
`229` | `wheat1` | `255,255,175` | `#ffffaf`
`230` | `cornsilk1` | `255,255,215` | `#ffffd7`
`231` | `grey100` | `255,255,255` | `#ffffff`
`232` | `grey3` | `8,8,8` | `#080808`
`233` | `grey7` | `18,18,18` | `#121212`
`234` | `grey11` | `28,28,28` | `#1c1c1c`
`235` | `grey15` | `38,38,38` | `#262626`
`236` | `grey19` | `48,48,48` | `#303030`
`237` | `grey23` | `58,58,58` | `#3a3a3a`
`238` | `grey27` | `68,68,68` | `#444444`
`239` | `grey30` | `78,78,78` | `#4e4e4e`
`240` | `grey35` | `88,88,88` | `#585858`
`241` | `grey39` | `98,98,98` | `#626262`
`242` | `grey42` | `108,108,108` | `#6c6c6c`
`243` | `grey46` | `118,118,118` | `#767676`
`244` | `grey50` | `128,128,128` | `#808080`
`245` | `grey54` | `138,138,138` | `#8a8a8a`
`246` | `grey58` | `148,148,148` | `#949494`
`247` | `grey62` | `158,158,158` | `#9e9e9e`
`248` | `grey66` | `168,168,168` | `#a8a8a8`
`249` | `grey70` | `178,178,178` | `#b2b2b2`
`250` | `grey74` | `188,188,188` | `#bcbcbc`
`251` | `grey78` | `198,198,198` | `#c6c6c6`
`252` | `grey82` | `208,208,208` | `#d0d0d0`
`253` | `grey85` | `218,218,218` | `#dadada`
`254` | `grey89` | `228,228,228` | `#e4e4e4`
`255` | `grey93` | `238,238,238` | `#eeeeee`

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

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

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

25
examples/Grid/Program.cs Normal file
View File

@@ -0,0 +1,25 @@
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);
}
}
}

View File

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

49
examples/Panel/Program.cs Normal file
View File

@@ -0,0 +1,49 @@
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,
});
}
}
}

90
examples/Table/Program.cs Normal file
View File

@@ -0,0 +1,90 @@
using Spectre.Console;
namespace TableExample
{
class Program
{
static void Main(string[] args)
{
// A simple table§
RenderSimpleTable();
// A big table
RenderBigTable();
// A complex table
RenderComplexTable();
}
private static void RenderComplexTable()
{
// Create simple table.
var simple = new Table { Border = BorderKind.Rounded };
simple.AddColumn(new TableColumn("[u]Foo[/]").Centered());
simple.AddColumn(new TableColumn("[u]Bar[/]"));
simple.AddColumn(new TableColumn("[u]Baz[/]"));
simple.AddRow("Hello", "[red]World![/]", "");
simple.AddRow("[blue]Bounjour[/]", "[white]le[/]", "[red]monde![/]");
simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
// Create other table.
var second = new Table { Border = BorderKind.Square };
second.AddColumn(new TableColumn("[u]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]"));
second.AddRow("Hello", "[red]World![/]", "");
second.AddRow(simple, new Text("Whaaat"), new Text("Lolz"));
second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table { Border = BorderKind.Rounded };
table.AddColumn(new TableColumn(new Panel("[u]Foo[/]")));
table.AddColumn(new TableColumn(new Panel("[u]Bar[/]")));
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]")));
// Add some rows
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty);
table.AddRow(second, new Text("Whaaat"), new Text("Lol"));
table.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);
AnsiConsole.Render(table);
}
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);
}
}
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

BIN
gfx/medium-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
gfx/small-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

2
scripts/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,24 @@
##########################################################
# Script that generates known colors and lookup tables.
##########################################################
$Output = Join-Path $PSScriptRoot "Temp"
$Source = Join-Path $PSScriptRoot "/../src/Spectre.Console"
if(!(Test-Path $Output -PathType Container)) {
New-Item -ItemType Directory -Path $Output | Out-Null
}
# Generate the files
Push-Location Generator
&dotnet run -- colors "$Output"
if(!$?) {
Pop-Location
Throw "An error occured when generating code."
}
Pop-Location
# Copy the files to the correct location
Copy-Item (Join-Path "$Output" "Color.Generated.cs") -Destination "$Source/Color.Generated.cs"
Copy-Item (Join-Path "$Output" "ColorPalette.Generated.cs") -Destination "$Source/Internal/Colors/ColorPalette.Generated.cs"
Copy-Item (Join-Path "$Output" "ColorTable.Generated.cs") -Destination "$Source/Internal/Colors/ColorTable.Generated.cs"

View File

@@ -0,0 +1,59 @@
using System;
using System.IO;
using Generator.Models;
using Scriban;
using Spectre.Cli;
using Spectre.IO;
namespace Generator.Commands
{
public sealed class ColorGeneratorCommand : Command<GeneratorCommandSettings>
{
private readonly IFileSystem _fileSystem;
public ColorGeneratorCommand()
{
_fileSystem = new FileSystem();
}
public override int Execute(CommandContext context, GeneratorCommandSettings settings)
{
var templates = new FilePath[]
{
"Templates/ColorPalette.Generated.template",
"Templates/Color.Generated.template",
"Templates/ColorTable.Generated.template"
};
// Read the color model.
var model = Color.Parse(File.ReadAllText("Data/colors.json"));
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
foreach (var templatePath in templates)
{
// Parse the Scriban template.
var template = Template.Parse(File.ReadAllText(templatePath.FullPath));
// Render the template with the model.
var result = template.Render(new { Colors = model });
// Write output to file
var file = output.CombineWithFilePath(templatePath.GetFilename().ChangeExtension(".cs"));
File.WriteAllText(file.FullPath, result);
}
return 0;
}
}
public sealed class GeneratorCommandSettings : CommandSettings
{
[CommandArgument(0, "<OUTPUT>")]
public string Output { get; set; }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Remove="out\**" />
<EmbeddedResource Remove="out\**" />
<None Remove="out\**" />
</ItemGroup>
<ItemGroup>
<None Update="Data\colors.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\ColorTable.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\Color.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\ColorPalette.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Scriban" Version="2.1.3" />
<PackageReference Include="Spectre.Cli" Version="0.36.1-preview.0.6" />
<PackageReference Include="Spectre.IO" Version="0.1.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30320.27
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator.csproj", "{5668D267-53E3-4B99-97AE-59AA597D22ED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.ActiveCfg = Debug|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.Build.0 = Debug|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.ActiveCfg = Debug|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.Build.0 = Debug|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.Build.0 = Release|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.ActiveCfg = Release|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.Build.0 = Release|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.ActiveCfg = Release|Any CPU
{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F37FDE3-D591-4D43-8DDE-2ED6BAB0A7B4}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace Generator.Models
{
public sealed class Color
{
public int Number { get; set; }
public string Hex { get; set; }
public string Name { get; set; }
public Rgb Rgb { get; set; }
public int R => Rgb.R;
public int G => Rgb.G;
public int B => Rgb.B;
public static IEnumerable<Color> Parse(string json)
{
var source = JsonConvert.DeserializeObject<List<Color>>(json);
var check = new Dictionary<string, Color>(StringComparer.OrdinalIgnoreCase);
foreach (var color in source.OrderBy(c => c.Number))
{
if (!check.ContainsKey(color.Name))
{
check.Add(color.Name, color);
}
else
{
var newName = (string)null;
for (int i = 1; i < 100; i++)
{
if (!check.ContainsKey($"{color.Name}_{i}"))
{
newName = $"{color.Name}_{i}";
break;
}
}
if (newName == null)
{
throw new InvalidOperationException("Impossible!");
}
check.Add(newName, color);
color.Name = newName;
}
}
return source;
}
}
public sealed class Rgb
{
public int R { get; set; }
public int G { get; set; }
public int B { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Generator.Models
{
public sealed class ColorModel
{
public List<Color> Colors { get; set; }
public ColorModel(IEnumerable<Color> colors)
{
Colors = new List<Color>(colors);
}
}
}

View File

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

View File

@@ -0,0 +1,19 @@
using Generator.Commands;
using Spectre.Cli;
namespace Generator
{
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp();
app.Configure(config =>
{
config.AddCommand<ColorGeneratorCommand>("colors");
});
return app.Run(args);
}
}
}

View File

@@ -0,0 +1,36 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console
{
/// <summary>
/// Represents a color.
/// </summary>
public partial struct Color
{
internal Color(byte number, byte red, byte green, byte blue, bool isDefault = false)
: this(red, green, blue)
{
Number = number;
IsDefault = isDefault;
}
{{~ for color in colors }}
/// <summary>
/// Gets the color "{{ color.name }}" (RGB {{ color.r }},{{ color.g }},{{ color.b }}).
/// </summary>
{{- if string.contains color.name "_" }}
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
{{- end}}
public static Color {{ color.name }} { get; } = new Color({{ color.number }}, {{ color.r }}, {{ color.g }}, {{ color.b }});
{{~ end ~}}
}
}

View File

@@ -0,0 +1,47 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static partial class ColorPalette
{
private static List<Color> GenerateLegacyPalette()
{
return new List<Color>
{
{{~ for number in 0..7 ~}}
Color.{{ colors[number].name }},
{{~ end ~}}
};
}
private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)
{
return new List<Color>(legacy)
{
{{~ for number in 8..15 ~}}
Color.{{ colors[number].name }},
{{~ end ~}}
};
}
private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)
{
return new List<Color>(standard)
{
{{~ for number in 16..255 ~}}
Color.{{ colors[number].name }},
{{~ end ~}}
};
}
}
}

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static partial class ColorTable
{
private static Dictionary<string, int> GenerateTable()
{
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{{~ for color in colors ~}}
{ "{{ string.downcase color.name }}", {{ color.number }} },
{{~ end ~}}
};
}
}
}

View File

@@ -75,3 +75,12 @@ 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
# IDE0004: Remove Unnecessary Cast
dotnet_diagnostic.IDE0004.severity = warning

View File

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

View File

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

View File

@@ -1,48 +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.WriteLine("Capabilities: {0}", AnsiConsole.Capabilities);
AnsiConsole.WriteLine($"Width={AnsiConsole.Width}, Height={AnsiConsole.Height}");
AnsiConsole.WriteLine("Good bye!");
AnsiConsole.WriteLine();
// 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.Foreground = Color.Chartreuse2;
console.Style = Styles.Underline | Styles.Bold;
console.WriteLine("Hello World!");
console.ResetColors();
console.ResetStyle();
console.WriteLine("Capabilities: {0}", AnsiConsole.Capabilities);
console.WriteLine($"Width={AnsiConsole.Width}, Height={AnsiConsole.Height}");
console.WriteLine("Good bye!");
console.WriteLine();
}
}
}

View File

@@ -21,3 +21,6 @@ dotnet_diagnostic.CA1034.severity = none
# CA2000: Dispose objects before losing scope
dotnet_diagnostic.CA2000.severity = none
# SA1118: Parameter should not span multiple lines
dotnet_diagnostic.SA1118.severity = none

View File

@@ -1,47 +0,0 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests
{
public partial class AnsiConsoleTests
{
[Theory]
[InlineData(Styles.Bold, "\u001b[1mHello World")]
[InlineData(Styles.Dim, "\u001b[2mHello World")]
[InlineData(Styles.Italic, "\u001b[3mHello World")]
[InlineData(Styles.Underline, "\u001b[4mHello World")]
[InlineData(Styles.Invert, "\u001b[7mHello World")]
[InlineData(Styles.Conceal, "\u001b[8mHello World")]
[InlineData(Styles.SlowBlink, "\u001b[5mHello World")]
[InlineData(Styles.RapidBlink, "\u001b[6mHello World")]
[InlineData(Styles.Strikethrough, "\u001b[9mHello World")]
public void Should_Write_Style_Correctly(Styles style, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
fixture.Console.Style = style;
// When
fixture.Console.Write("Hello World");
// Then
fixture.Output.ShouldBe(expected);
}
[Theory]
[InlineData(Styles.Bold | Styles.Underline, "\u001b[1;4mHello World")]
[InlineData(Styles.Bold | Styles.Underline | Styles.Conceal, "\u001b[1;4;8mHello World")]
public void Should_Write_Combined_Styles_Correctly(Styles style, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
fixture.Console.Style = style;
// When
fixture.Console.Write("Hello World");
// Then
fixture.Output.ShouldBe(expected);
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace Spectre.Console.Tests
{
public static class StringExtensions
{
public static string NormalizeLineEndings(this string text)
{
return text?.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase)
?.Replace("\r", string.Empty, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -11,16 +11,17 @@ namespace Spectre.Console.Tests
public string Output => _writer.ToString();
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes)
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
{
_writer = new StringWriter();
Console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = ansi,
ColorSystem = (ColorSystemSupport)system,
Out = _writer,
});
Console = new ConsoleWithWidth(
AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = ansi,
ColorSystem = (ColorSystemSupport)system,
Out = _writer,
}), width);
}
public void Dispose()

View File

@@ -0,0 +1,31 @@
using System.Text;
namespace Spectre.Console.Tests
{
public sealed class ConsoleWithWidth : IAnsiConsole
{
private readonly IAnsiConsole _console;
public Capabilities Capabilities => _console.Capabilities;
public int Width { get; }
public int Height => _console.Height;
public Encoding Encoding => _console.Encoding;
public Decoration Decoration { get => _console.Decoration; set => _console.Decoration = value; }
public Color Foreground { get => _console.Foreground; set => _console.Foreground = value; }
public Color Background { get => _console.Background; set => _console.Background = value; }
public ConsoleWithWidth(IAnsiConsole console, int width)
{
_console = console;
Width = width;
}
public void Write(string text)
{
_console.Write(text);
}
}
}

View File

@@ -0,0 +1,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);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,89 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
public sealed class Markup
{
[Theory]
[InlineData("[yellow]Hello[/]", "Hello")]
[InlineData("[yellow]Hello [italic]World[/]![/]", "Hello World!")]
public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
// When
fixture.Console.Markup(markup);
// Then
fixture.Output.ShouldBe(expected);
}
[Theory]
[InlineData("[yellow]Hello [[ World[/]", "Hello [ World")]
public void Should_Be_Able_To_Escape_Tags(string markup, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
// When
fixture.Console.Markup(markup);
// Then
fixture.Output.ShouldBe(expected);
}
[Theory]
[InlineData("[yellow]Hello[", "Encountered malformed markup tag at position 14.")]
[InlineData("[yellow]Hello[/", "Encountered malformed markup tag at position 15.")]
[InlineData("[yellow]Hello[/foo", "Encountered malformed markup tag at position 15.")]
[InlineData("[yellow Hello", "Encountered malformed markup tag at position 13.")]
public void Should_Throw_If_Encounters_Malformed_Tag(string markup, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
// When
var result = Record.Exception(() => fixture.Console.Markup(markup));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe(expected);
}
[Fact]
public void Should_Throw_If_Tags_Are_Unbalanced()
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
// When
var result = Record.Exception(() => fixture.Console.Markup("[yellow][blue]Hello[/]"));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe("Unbalanced markup stack. Did you forget to close a tag?");
}
[Fact]
public void Should_Throw_If_Encounters_Closing_Tag()
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes);
// When
var result = Record.Exception(() => fixture.Console.Markup("Hello[/]World"));
// Then
result.ShouldBeOfType<InvalidOperationException>()
.Message.ShouldBe("Encountered closing tag when none was expected near position 5.");
}
}
}
}

View File

@@ -0,0 +1,47 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
[Theory]
[InlineData(Decoration.Bold, "\u001b[1mHello World")]
[InlineData(Decoration.Dim, "\u001b[2mHello World")]
[InlineData(Decoration.Italic, "\u001b[3mHello World")]
[InlineData(Decoration.Underline, "\u001b[4mHello World")]
[InlineData(Decoration.Invert, "\u001b[7mHello World")]
[InlineData(Decoration.Conceal, "\u001b[8mHello World")]
[InlineData(Decoration.SlowBlink, "\u001b[5mHello World")]
[InlineData(Decoration.RapidBlink, "\u001b[6mHello World")]
[InlineData(Decoration.Strikethrough, "\u001b[9mHello World")]
public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
fixture.Console.Decoration = decoration;
// When
fixture.Console.Write("Hello World");
// Then
fixture.Output.ShouldBe(expected);
}
[Theory]
[InlineData(Decoration.Bold | Decoration.Underline, "\u001b[1;4mHello World")]
[InlineData(Decoration.Bold | Decoration.Underline | Decoration.Conceal, "\u001b[1;4;8mHello World")]
public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor);
fixture.Console.Decoration = decoration;
// When
fixture.Console.Write("Hello World");
// Then
fixture.Output.ShouldBe(expected);
}
}
}

View File

@@ -3,18 +3,18 @@ using System.Globalization;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests
namespace Spectre.Console.Tests.Unit
{
public partial class AnsiConsoleTests
{
[Fact]
public void Should_Combine_Style_And_Colors()
public void Should_Combine_Decoration_And_Colors()
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Style = Styles.Italic;
fixture.Console.Decoration = Decoration.Italic;
// When
fixture.Console.Write("Hello");
@@ -30,7 +30,7 @@ namespace Spectre.Console.Tests
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.Default;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Style = Styles.Italic;
fixture.Console.Decoration = Decoration.Italic;
// When
fixture.Console.Write("Hello");
@@ -46,7 +46,7 @@ namespace Spectre.Console.Tests
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.Default;
fixture.Console.Style = Styles.Italic;
fixture.Console.Decoration = Decoration.Italic;
// When
fixture.Console.Write("Hello");
@@ -56,13 +56,13 @@ namespace Spectre.Console.Tests
}
[Fact]
public void Should_Not_Include_Style_If_Set_To_None()
public void Should_Not_Include_Decoration_If_Set_To_None()
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Style = Styles.None;
fixture.Console.Decoration = Decoration.None;
// When
fixture.Console.Write("Hello");
@@ -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("Hello\nWorld\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("Hello\nWorld\n");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]

View File

@@ -0,0 +1,42 @@
using System;
using Shouldly;
using Spectre.Console.Rendering;
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");
}
}
}
}

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

View File

@@ -0,0 +1,233 @@
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_Add_Empty_Row_At_The_End()
{
// Given
var console = new PlainConsole(width: 80);
var grid = new Grid();
grid.AddColumns(2);
grid.AddRow("Foo", "Bar");
grid.AddEmptyRow();
grid.AddRow("Qux", "Corgi");
grid.AddEmptyRow();
// When
console.Render(grid);
// Then
console.Lines.Count.ShouldBe(4);
console.Lines[0].ShouldBe("Foo Bar ");
console.Lines[1].ShouldBe(" ");
console.Lines[2].ShouldBe("Qux Corgi");
console.Lines[3].ShouldBe(" ");
}
}
[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.");
}
}
}

View File

@@ -0,0 +1,178 @@
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").RightAligned())
{
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").Centered())
{
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("└─────────────────┘");
}
}
}

View File

@@ -0,0 +1,92 @@
using Shouldly;
using Spectre.Console.Rendering;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class SegmentTests
{
public sealed class TheSplitMethod
{
[Fact]
public void Should_Split_Segment_Correctly()
{
// Given
var style = new Style(Color.Red, Color.Green, Decoration.Bold);
var segment = new Segment("Foo Bar", style);
// When
var (first, second) = segment.Split(3);
// Then
first.Text.ShouldBe("Foo");
first.Style.ShouldBe(style);
second.Text.ShouldBe(" Bar");
second.Style.ShouldBe(style);
}
}
public sealed class TheSplitLinesMethod
{
[Fact]
public void Should_Split_Segment()
{
var lines = Segment.SplitLines(
new[]
{
new Segment("Foo"),
new Segment("Bar"),
new Segment("\n"),
new Segment("Baz"),
new Segment("Qux"),
new Segment("\n"),
new Segment("Corgi"),
});
// Then
lines.Count.ShouldBe(3);
lines[0].Count.ShouldBe(2);
lines[0][0].Text.ShouldBe("Foo");
lines[0][1].Text.ShouldBe("Bar");
lines[1].Count.ShouldBe(2);
lines[1][0].Text.ShouldBe("Baz");
lines[1][1].Text.ShouldBe("Qux");
lines[2].Count.ShouldBe(1);
lines[2][0].Text.ShouldBe("Corgi");
}
[Fact]
public void Should_Split_Segments_With_Linebreak_In_Text()
{
var lines = Segment.SplitLines(
new[]
{
new Segment("Foo\n"),
new Segment("Bar\n"),
new Segment("Baz"),
new Segment("Qux\n"),
new Segment("Corgi"),
});
// Then
lines.Count.ShouldBe(4);
lines[0].Count.ShouldBe(1);
lines[0][0].Text.ShouldBe("Foo");
lines[1].Count.ShouldBe(1);
lines[1][0].Text.ShouldBe("Bar");
lines[2].Count.ShouldBe(2);
lines[2][0].Text.ShouldBe("Baz");
lines[2][1].Text.ShouldBe("Qux");
lines[3].Count.ShouldBe(1);
lines[3][0].Text.ShouldBe("Corgi");
}
}
}
}

View File

@@ -0,0 +1,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();
}
}
}
}

View File

@@ -0,0 +1,388 @@
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_String_Rows_Are_Null()
{
// Given
var table = new Table();
// When
var result = Record.Exception(() => table.AddRow((string[])null));
// Then
result.ShouldBeOfType<ArgumentNullException>()
.ParamName.ShouldBe("columns");
}
[Fact]
public void Should_Throw_If_Renderable_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("└─────┴─────┴────────┘");
}
}
}

View File

@@ -0,0 +1,81 @@
using System.Text;
using Shouldly;
using Spectre.Console.Rendering;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class TextTests
{
[Fact]
public void Should_Consider_The_Longest_Word_As_Minimum_Width()
{
var text = new Text("Foo Bar Baz\nQux\nLol mobile");
var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80);
result.Min.ShouldBe(6);
}
[Fact]
public void Should_Consider_The_Longest_Line_As_Maximum_Width()
{
var text = new Text("Foo Bar Baz\nQux\nLol mobile");
var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80);
result.Max.ShouldBe(11);
}
[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");
}
[Theory]
[InlineData("Hello\n\nWorld\n\n")]
[InlineData("Hello\r\n\r\nWorld\r\n\r\n")]
public void Should_Write_Line_Breaks(string input)
{
// Given
var fixture = new PlainConsole(width: 5);
var text = new Text(input);
// When
fixture.Render(text);
// Then
fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n");
}
[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);
}
}
}

View File

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

View File

@@ -0,0 +1,52 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(string format, params object[] args)
{
Console.Markup(format, args);
}
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(IFormatProvider provider, string format, params object[] args)
{
Console.Markup(provider, format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(string format, params object[] args)
{
Console.MarkupLine(format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(IFormatProvider provider, string format, params object[] args)
{
Console.MarkupLine(provider, format, args);
}
}
}

View File

@@ -0,0 +1,19 @@
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// Renders the specified object to the console.
/// </summary>
/// <param name="renderable">The object to render.</param>
public static void Render(IRenderable renderable)
{
Console.Render(renderable);
}
}
}

View File

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

View File

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

View File

@@ -3,8 +3,7 @@ using System.IO;
namespace Spectre.Console
{
/// <summary>
/// Settings used by <see cref="ConsoleBuilder"/>
/// when building a <see cref="IAnsiConsole"/>.
/// Settings used when building a <see cref="IAnsiConsole"/>.
/// </summary>
public sealed class AnsiConsoleSettings
{
@@ -22,6 +21,6 @@ namespace Spectre.Console
/// <summary>
/// Gets or sets the out buffer.
/// </summary>
public TextWriter Out { get; set; }
public TextWriter? Out { get; set; }
}
}

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

View File

@@ -1,6 +1,6 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Globalization;
using Spectre.Console.Internal;
namespace Spectre.Console
@@ -17,7 +17,7 @@ namespace Spectre.Console
static Color()
{
Default = new Color(0, "default", 0, 0, 0, true);
Default = new Color(0, 0, 0, 0, true);
}
/// <summary>
@@ -35,11 +35,6 @@ namespace Spectre.Console
/// </summary>
public byte B { get; }
/// <summary>
/// Gets the name of the color, if any.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the number of the color, if any.
/// </summary>
@@ -62,7 +57,6 @@ namespace Spectre.Console
G = green;
B = blue;
IsDefault = false;
Name = null;
Number = null;
}
@@ -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);
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Globalization;
using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static partial class ConsoleExtensions
{
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(this IAnsiConsole console, string format, params object[] args)
{
Markup(console, CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args)
{
console.Render(MarkupParser.Parse(string.Format(provider, format, args)));
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(this IAnsiConsole console, string format, params object[] args)
{
MarkupLine(console, CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args)
{
Markup(console, provider, format, args);
console.WriteLine();
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
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);
}
}
}
}
}

View File

@@ -8,7 +8,7 @@ namespace Spectre.Console
public static partial class ConsoleExtensions
{
/// <summary>
/// Resets both colors and style for the console.
/// Resets colors and text decorations.
/// </summary>
/// <param name="console">The console to reset.</param>
public static void Reset(this IAnsiConsole console)
@@ -19,25 +19,25 @@ namespace Spectre.Console
}
console.ResetColors();
console.ResetStyle();
console.ResetDecoration();
}
/// <summary>
/// Resets the current style back to the default one.
/// Resets the current applied text decorations.
/// </summary>
/// <param name="console">The console to reset the style for.</param>
public static void ResetStyle(this IAnsiConsole console)
/// <param name="console">The console to reset the text decorations for.</param>
public static void ResetDecoration(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Style = Styles.None;
console.Decoration = Decoration.None;
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// Resets the current applied foreground and background colors.
/// </summary>
/// <param name="console">The console to reset colors for.</param>
public static void ResetColors(this IAnsiConsole console)

View File

@@ -1,18 +1,20 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console
{
/// <summary>
/// Represents a style.
/// Represents text decoration.
/// </summary>
/// <remarks>
/// Support for different styles is up to the terminal.
/// Support for text decorations is up to the terminal.
/// </remarks>
[Flags]
public enum Styles
[SuppressMessage("Naming", "CA1714:Flags enums should have plural names")]
public enum Decoration
{
/// <summary>
/// No style.
/// No text decoration.
/// </summary>
None = 0,

View File

@@ -1,3 +1,5 @@
using System.Text;
namespace Spectre.Console
{
/// <summary>
@@ -8,22 +10,27 @@ namespace Spectre.Console
/// <summary>
/// Gets the console's capabilities.
/// </summary>
public AnsiConsoleCapabilities Capabilities { get; }
Capabilities Capabilities { get; }
/// <summary>
/// Gets the buffer width of the console.
/// </summary>
public int Width { get; }
int Width { get; }
/// <summary>
/// Gets the buffer height of the console.
/// </summary>
public int Height { get; }
int Height { get; }
/// <summary>
/// Gets or sets the current style.
/// Gets the console output encoding.
/// </summary>
Styles Style { get; set; }
Encoding Encoding { get; }
/// <summary>
/// Gets or sets the current text decoration.
/// </summary>
Decoration Decoration { get; set; }
/// <summary>
/// Gets or sets the current foreground.

View File

@@ -7,11 +7,11 @@ namespace Spectre.Console.Internal
public static string GetAnsi(
ColorSystem system,
string text,
Styles style,
Decoration decoration,
Color foreground,
Color background)
{
var codes = AnsiStyleBuilder.GetAnsiCodes(style);
var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration);
// Got foreground?
if (foreground != Color.Default)

View File

@@ -2,52 +2,52 @@ using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class AnsiStyleBuilder
internal static class AnsiDecorationBuilder
{
// TODO: Rewrite this to not yield
public static IEnumerable<byte> GetAnsiCodes(Styles style)
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
{
if ((style & Styles.Bold) != 0)
if ((decoration & Decoration.Bold) != 0)
{
yield return 1;
}
if ((style & Styles.Dim) != 0)
if ((decoration & Decoration.Dim) != 0)
{
yield return 2;
}
if ((style & Styles.Italic) != 0)
if ((decoration & Decoration.Italic) != 0)
{
yield return 3;
}
if ((style & Styles.Underline) != 0)
if ((decoration & Decoration.Underline) != 0)
{
yield return 4;
}
if ((style & Styles.SlowBlink) != 0)
if ((decoration & Decoration.SlowBlink) != 0)
{
yield return 5;
}
if ((style & Styles.RapidBlink) != 0)
if ((decoration & Decoration.RapidBlink) != 0)
{
yield return 6;
}
if ((style & Styles.Invert) != 0)
if ((decoration & Decoration.Invert) != 0)
{
yield return 7;
}
if ((style & Styles.Conceal) != 0)
if ((decoration & Decoration.Conceal) != 0)
{
yield return 8;
}
if ((style & Styles.Strikethrough) != 0)
if ((decoration & Decoration.Strikethrough) != 0)
{
yield return 9;
}

View File

@@ -13,7 +13,7 @@ namespace Spectre.Console.Internal
{
internal static class AnsiDetector
{
private static readonly Regex[] Regexes = new[]
private static readonly Regex[] _regexes = new[]
{
new Regex("^xterm"), // xterm, PuTTY, Mintty
new Regex("^rxvt"), // RXVT
@@ -32,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;

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text;
namespace Spectre.Console.Internal
{
@@ -8,8 +9,9 @@ namespace Spectre.Console.Internal
private readonly TextWriter _out;
private readonly ColorSystem _system;
public AnsiConsoleCapabilities Capabilities { get; }
public Styles Style { get; set; }
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public Decoration Decoration { get; set; }
public Color Foreground { get; set; }
public Color Background { get; set; }
@@ -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);
}
}
}
}
}

View File

@@ -0,0 +1,294 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated 2020-08-03 15:17
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static partial class ColorPalette
{
private static List<Color> GenerateLegacyPalette()
{
return new List<Color>
{
Color.Black,
Color.Maroon,
Color.Green,
Color.Olive,
Color.Navy,
Color.Purple,
Color.Teal,
Color.Silver,
};
}
private static List<Color> GenerateStandardPalette(IReadOnlyList<Color> legacy)
{
return new List<Color>(legacy)
{
Color.Grey,
Color.Red,
Color.Lime,
Color.Yellow,
Color.Blue,
Color.Fuchsia,
Color.Aqua,
Color.White,
};
}
private static List<Color> GenerateEightBitPalette(IReadOnlyList<Color> standard)
{
return new List<Color>(standard)
{
Color.Grey0,
Color.NavyBlue,
Color.DarkBlue,
Color.Blue3,
Color.Blue3_1,
Color.Blue1,
Color.DarkGreen,
Color.DeepSkyBlue4,
Color.DeepSkyBlue4_1,
Color.DeepSkyBlue4_2,
Color.DodgerBlue3,
Color.DodgerBlue2,
Color.Green4,
Color.SpringGreen4,
Color.Turquoise4,
Color.DeepSkyBlue3,
Color.DeepSkyBlue3_1,
Color.DodgerBlue1,
Color.Green3,
Color.SpringGreen3,
Color.DarkCyan,
Color.LightSeaGreen,
Color.DeepSkyBlue2,
Color.DeepSkyBlue1,
Color.Green3_1,
Color.SpringGreen3_1,
Color.SpringGreen2,
Color.Cyan3,
Color.DarkTurquoise,
Color.Turquoise2,
Color.Green1,
Color.SpringGreen2_1,
Color.SpringGreen1,
Color.MediumSpringGreen,
Color.Cyan2,
Color.Cyan1,
Color.DarkRed,
Color.DeepPink4,
Color.Purple4,
Color.Purple4_1,
Color.Purple3,
Color.BlueViolet,
Color.Orange4,
Color.Grey37,
Color.MediumPurple4,
Color.SlateBlue3,
Color.SlateBlue3_1,
Color.RoyalBlue1,
Color.Chartreuse4,
Color.DarkSeaGreen4,
Color.PaleTurquoise4,
Color.SteelBlue,
Color.SteelBlue3,
Color.CornflowerBlue,
Color.Chartreuse3,
Color.DarkSeaGreen4_1,
Color.CadetBlue,
Color.CadetBlue_1,
Color.SkyBlue3,
Color.SteelBlue1,
Color.Chartreuse3_1,
Color.PaleGreen3,
Color.SeaGreen3,
Color.Aquamarine3,
Color.MediumTurquoise,
Color.SteelBlue1_1,
Color.Chartreuse2,
Color.SeaGreen2,
Color.SeaGreen1,
Color.SeaGreen1_1,
Color.Aquamarine1,
Color.DarkSlateGray2,
Color.DarkRed_1,
Color.DeepPink4_1,
Color.DarkMagenta,
Color.DarkMagenta_1,
Color.DarkViolet,
Color.Purple_1,
Color.Orange4_1,
Color.LightPink4,
Color.Plum4,
Color.MediumPurple3,
Color.MediumPurple3_1,
Color.SlateBlue1,
Color.Yellow4,
Color.Wheat4,
Color.Grey53,
Color.LightSlateGrey,
Color.MediumPurple,
Color.LightSlateBlue,
Color.Yellow4_1,
Color.DarkOliveGreen3,
Color.DarkSeaGreen,
Color.LightSkyBlue3,
Color.LightSkyBlue3_1,
Color.SkyBlue2,
Color.Chartreuse2_1,
Color.DarkOliveGreen3_1,
Color.PaleGreen3_1,
Color.DarkSeaGreen3,
Color.DarkSlateGray3,
Color.SkyBlue1,
Color.Chartreuse1,
Color.LightGreen,
Color.LightGreen_1,
Color.PaleGreen1,
Color.Aquamarine1_1,
Color.DarkSlateGray1,
Color.Red3,
Color.DeepPink4_2,
Color.MediumVioletRed,
Color.Magenta3,
Color.DarkViolet_1,
Color.Purple_2,
Color.DarkOrange3,
Color.IndianRed,
Color.HotPink3,
Color.MediumOrchid3,
Color.MediumOrchid,
Color.MediumPurple2,
Color.DarkGoldenrod,
Color.LightSalmon3,
Color.RosyBrown,
Color.Grey63,
Color.MediumPurple2_1,
Color.MediumPurple1,
Color.Gold3,
Color.DarkKhaki,
Color.NavajoWhite3,
Color.Grey69,
Color.LightSteelBlue3,
Color.LightSteelBlue,
Color.Yellow3,
Color.DarkOliveGreen3_2,
Color.DarkSeaGreen3_1,
Color.DarkSeaGreen2,
Color.LightCyan3,
Color.LightSkyBlue1,
Color.GreenYellow,
Color.DarkOliveGreen2,
Color.PaleGreen1_1,
Color.DarkSeaGreen2_1,
Color.DarkSeaGreen1,
Color.PaleTurquoise1,
Color.Red3_1,
Color.DeepPink3,
Color.DeepPink3_1,
Color.Magenta3_1,
Color.Magenta3_2,
Color.Magenta2,
Color.DarkOrange3_1,
Color.IndianRed_1,
Color.HotPink3_1,
Color.HotPink2,
Color.Orchid,
Color.MediumOrchid1,
Color.Orange3,
Color.LightSalmon3_1,
Color.LightPink3,
Color.Pink3,
Color.Plum3,
Color.Violet,
Color.Gold3_1,
Color.LightGoldenrod3,
Color.Tan,
Color.MistyRose3,
Color.Thistle3,
Color.Plum2,
Color.Yellow3_1,
Color.Khaki3,
Color.LightGoldenrod2,
Color.LightYellow3,
Color.Grey84,
Color.LightSteelBlue1,
Color.Yellow2,
Color.DarkOliveGreen1,
Color.DarkOliveGreen1_1,
Color.DarkSeaGreen1_1,
Color.Honeydew2,
Color.LightCyan1,
Color.Red1,
Color.DeepPink2,
Color.DeepPink1,
Color.DeepPink1_1,
Color.Magenta2_1,
Color.Magenta1,
Color.OrangeRed1,
Color.IndianRed1,
Color.IndianRed1_1,
Color.HotPink,
Color.HotPink_1,
Color.MediumOrchid1_1,
Color.DarkOrange,
Color.Salmon1,
Color.LightCoral,
Color.PaleVioletRed1,
Color.Orchid2,
Color.Orchid1,
Color.Orange1,
Color.SandyBrown,
Color.LightSalmon1,
Color.LightPink1,
Color.Pink1,
Color.Plum1,
Color.Gold1,
Color.LightGoldenrod2_1,
Color.LightGoldenrod2_2,
Color.NavajoWhite1,
Color.MistyRose1,
Color.Thistle1,
Color.Yellow1,
Color.LightGoldenrod1,
Color.Khaki1,
Color.Wheat1,
Color.Cornsilk1,
Color.Grey100,
Color.Grey3,
Color.Grey7,
Color.Grey11,
Color.Grey15,
Color.Grey19,
Color.Grey23,
Color.Grey27,
Color.Grey30,
Color.Grey35,
Color.Grey39,
Color.Grey42,
Color.Grey46,
Color.Grey50,
Color.Grey54,
Color.Grey58,
Color.Grey62,
Color.Grey66,
Color.Grey70,
Color.Grey74,
Color.Grey78,
Color.Grey82,
Color.Grey85,
Color.Grey89,
Color.Grey93,
};
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
namespace Spectre.Console.Internal
{
internal static class ColorPalette
internal static partial class ColorPalette
{
public static IReadOnlyList<Color> Legacy { get; }
public static IReadOnlyList<Color> Standard { get; }
@@ -12,92 +12,15 @@ namespace Spectre.Console.Internal
static ColorPalette()
{
Legacy = new List<Color>
{
Color.Black, Color.Maroon, Color.Green, Color.Olive,
Color.Navy, Color.Purple, Color.Teal, Color.Silver,
};
Standard = new List<Color>(Legacy)
{
Color.Grey, Color.Red, Color.Lime, Color.Yellow,
Color.Blue, Color.Fuchsia, Color.Aqua, Color.White,
};
EightBit = new List<Color>(Standard)
{
Color.Grey0, Color.NavyBlue, Color.DarkBlue, Color.Blue3,
Color.Blue3_1, Color.Blue1, Color.DarkGreen, Color.DeepSkyBlue4,
Color.DeepSkyBlue4_1, Color.DeepSkyBlue4_2, Color.DodgerBlue3, Color.DodgerBlue2,
Color.Green4, Color.SpringGreen4, Color.Turquoise4, Color.DeepSkyBlue3,
Color.DeepSkyBlue3_1, Color.DodgerBlue1, Color.Green3, Color.SpringGreen3,
Color.DarkCyan, Color.LightSeaGreen, Color.DeepSkyBlue2, Color.DeepSkyBlue1,
Color.Green3_1, Color.SpringGreen3_1, Color.SpringGreen2, Color.Cyan3,
Color.DarkTurquoise, Color.Turquoise2, Color.Green1, Color.SpringGreen2_1,
Color.SpringGreen1, Color.MediumSpringGreen, Color.Cyan2, Color.Cyan1,
Color.DarkRed, Color.DeepPink4, Color.Purple4, Color.Purple4_1,
Color.Purple3, Color.BlueViolet, Color.Orange4, Color.Grey37,
Color.MediumPurple4, Color.SlateBlue3, Color.SlateBlue3_1, Color.RoyalBlue1,
Color.Chartreuse4, Color.DarkSeaGreen4, Color.PaleTurquoise4, Color.SteelBlue,
Color.SteelBlue3, Color.CornflowerBlue, Color.Chartreuse3, Color.DarkSeaGreen4_1,
Color.CadetBlue, Color.CadetBlue_1, Color.SkyBlue3, Color.SteelBlue1,
Color.Chartreuse3_1, Color.PaleGreen3, Color.SeaGreen3, Color.Aquamarine3,
Color.MediumTurquoise, Color.SteelBlue1_1, Color.Chartreuse2, Color.SeaGreen2,
Color.SeaGreen1, Color.SeaGreen1_1, Color.Aquamarine1, Color.DarkSlateGray2,
Color.DarkRed_1, Color.DeepPink4_1, Color.DarkMagenta, Color.DarkMagenta_1,
Color.DarkViolet, Color.Purple_1, Color.Orange4_1, Color.LightPink4,
Color.Plum4, Color.MediumPurple3, Color.MediumPurple3_1, Color.SlateBlue1,
Color.Yellow4, Color.Wheat4, Color.Grey53, Color.LightSlateGrey,
Color.MediumPurple, Color.LightSlateBlue, Color.Yellow4_1, Color.DarkOliveGreen3,
Color.DarkSeaGreen, Color.LightSkyBlue3, Color.LightSkyBlue3_1, Color.SkyBlue2,
Color.Chartreuse2_1, Color.DarkOliveGreen3_1, Color.PaleGreen3_1, Color.DarkSeaGreen3,
Color.DarkSlateGray3, Color.SkyBlue1, Color.Chartreuse1, Color.LightGreen,
Color.LightGreen_1, Color.PaleGreen1, Color.Aquamarine1_1, Color.DarkSlateGray1,
Color.Red3, Color.DeepPink4_2, Color.MediumVioletRed, Color.Magenta3,
Color.DarkViolet_1, Color.Purple_2, Color.DarkOrange3, Color.IndianRed,
Color.HotPink3, Color.MediumOrchid3, Color.MediumOrchid, Color.MediumPurple2,
Color.DarkGoldenrod, Color.LightSalmon3, Color.RosyBrown, Color.Grey63,
Color.MediumPurple2_1, Color.MediumPurple1, Color.Gold3, Color.DarkKhaki,
Color.NavajoWhite3, Color.Grey69, Color.LightSteelBlue3, Color.LightSteelBlue,
Color.Yellow3, Color.DarkOliveGreen3_2, Color.DarkSeaGreen3_1, Color.DarkSeaGreen2,
Color.LightCyan3, Color.LightSkyBlue1, Color.GreenYellow, Color.DarkOliveGreen2,
Color.PaleGreen1_1, Color.DarkSeaGreen2_1, Color.DarkSeaGreen1, Color.PaleTurquoise1,
Color.Red3_1, Color.DeepPink3, Color.DeepPink3_1, Color.Magenta3_1,
Color.Magenta3_2, Color.Magenta2, Color.DarkOrange3_1, Color.IndianRed_1,
Color.HotPink3_1, Color.HotPink2, Color.Orchid, Color.MediumOrchid1,
Color.Orange3, Color.LightSalmon3_1, Color.LightPink3, Color.Pink3,
Color.Plum3, Color.Violet, Color.Gold3_1, Color.LightGoldenrod3,
Color.Tan, Color.MistyRose3, Color.Thistle3, Color.Plum2,
Color.Yellow3_1, Color.Khaki3, Color.LightGoldenrod2, Color.LightYellow3,
Color.Grey84, Color.LightSteelBlue1, Color.Yellow2, Color.DarkOliveGreen1,
Color.DarkOliveGreen1_1, Color.DarkSeaGreen1_1, Color.Honeydew2, Color.LightCyan1,
Color.Red1, Color.DeepPink2, Color.DeepPink1, Color.DeepPink1_1,
Color.Magenta2_1, Color.Magenta1, Color.OrangeRed1, Color.IndianRed1,
Color.IndianRed1_1, Color.HotPink, Color.HotPink_1, Color.MediumOrchid1_1,
Color.DarkOrange, Color.Salmon1, Color.LightCoral, Color.PaleVioletRed1,
Color.Orchid2, Color.Orchid1, Color.Orange1, Color.SandyBrown,
Color.LightSalmon1, Color.LightPink1, Color.Pink1, Color.Plum1,
Color.Gold1, Color.LightGoldenrod2_1, Color.LightGoldenrod2_2, Color.NavajoWhite1,
Color.MistyRose1, Color.Thistle1, Color.Yellow1, Color.LightGoldenrod1,
Color.Khaki1, Color.Wheat1, Color.Cornsilk1, Color.Grey100,
Color.Grey3, Color.Grey7, Color.Grey11, Color.Grey15,
Color.Grey19, Color.Grey23, Color.Grey27, Color.Grey30,
Color.Grey35, Color.Grey39, Color.Grey42, Color.Grey46,
Color.Grey50, Color.Grey54, Color.Grey58, Color.Grey62,
Color.Grey66, Color.Grey70, Color.Grey74, Color.Grey78,
Color.Grey82, Color.Grey85, Color.Grey89, Color.Grey93,
};
Legacy = GenerateLegacyPalette();
Standard = GenerateStandardPalette(Legacy);
EightBit = GenerateEightBitPalette(Standard);
}
internal static Color ExactOrClosest(ColorSystem system, Color color)
{
var exact = Exact(system, color);
if (exact != null)
{
return exact.Value;
}
return Closest(system, color);
return exact ?? Closest(system, color);
}
private static Color? Exact(ColorSystem system, Color color)

View File

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

View File

@@ -0,0 +1,281 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated 2020-08-03 15:17
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static partial class ColorTable
{
private static Dictionary<string, int> GenerateTable()
{
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
{ "black", 0 },
{ "maroon", 1 },
{ "green", 2 },
{ "olive", 3 },
{ "navy", 4 },
{ "purple", 5 },
{ "teal", 6 },
{ "silver", 7 },
{ "grey", 8 },
{ "red", 9 },
{ "lime", 10 },
{ "yellow", 11 },
{ "blue", 12 },
{ "fuchsia", 13 },
{ "aqua", 14 },
{ "white", 15 },
{ "grey0", 16 },
{ "navyblue", 17 },
{ "darkblue", 18 },
{ "blue3", 19 },
{ "blue3_1", 20 },
{ "blue1", 21 },
{ "darkgreen", 22 },
{ "deepskyblue4", 23 },
{ "deepskyblue4_1", 24 },
{ "deepskyblue4_2", 25 },
{ "dodgerblue3", 26 },
{ "dodgerblue2", 27 },
{ "green4", 28 },
{ "springgreen4", 29 },
{ "turquoise4", 30 },
{ "deepskyblue3", 31 },
{ "deepskyblue3_1", 32 },
{ "dodgerblue1", 33 },
{ "green3", 34 },
{ "springgreen3", 35 },
{ "darkcyan", 36 },
{ "lightseagreen", 37 },
{ "deepskyblue2", 38 },
{ "deepskyblue1", 39 },
{ "green3_1", 40 },
{ "springgreen3_1", 41 },
{ "springgreen2", 42 },
{ "cyan3", 43 },
{ "darkturquoise", 44 },
{ "turquoise2", 45 },
{ "green1", 46 },
{ "springgreen2_1", 47 },
{ "springgreen1", 48 },
{ "mediumspringgreen", 49 },
{ "cyan2", 50 },
{ "cyan1", 51 },
{ "darkred", 52 },
{ "deeppink4", 53 },
{ "purple4", 54 },
{ "purple4_1", 55 },
{ "purple3", 56 },
{ "blueviolet", 57 },
{ "orange4", 58 },
{ "grey37", 59 },
{ "mediumpurple4", 60 },
{ "slateblue3", 61 },
{ "slateblue3_1", 62 },
{ "royalblue1", 63 },
{ "chartreuse4", 64 },
{ "darkseagreen4", 65 },
{ "paleturquoise4", 66 },
{ "steelblue", 67 },
{ "steelblue3", 68 },
{ "cornflowerblue", 69 },
{ "chartreuse3", 70 },
{ "darkseagreen4_1", 71 },
{ "cadetblue", 72 },
{ "cadetblue_1", 73 },
{ "skyblue3", 74 },
{ "steelblue1", 75 },
{ "chartreuse3_1", 76 },
{ "palegreen3", 77 },
{ "seagreen3", 78 },
{ "aquamarine3", 79 },
{ "mediumturquoise", 80 },
{ "steelblue1_1", 81 },
{ "chartreuse2", 82 },
{ "seagreen2", 83 },
{ "seagreen1", 84 },
{ "seagreen1_1", 85 },
{ "aquamarine1", 86 },
{ "darkslategray2", 87 },
{ "darkred_1", 88 },
{ "deeppink4_1", 89 },
{ "darkmagenta", 90 },
{ "darkmagenta_1", 91 },
{ "darkviolet", 92 },
{ "purple_1", 93 },
{ "orange4_1", 94 },
{ "lightpink4", 95 },
{ "plum4", 96 },
{ "mediumpurple3", 97 },
{ "mediumpurple3_1", 98 },
{ "slateblue1", 99 },
{ "yellow4", 100 },
{ "wheat4", 101 },
{ "grey53", 102 },
{ "lightslategrey", 103 },
{ "mediumpurple", 104 },
{ "lightslateblue", 105 },
{ "yellow4_1", 106 },
{ "darkolivegreen3", 107 },
{ "darkseagreen", 108 },
{ "lightskyblue3", 109 },
{ "lightskyblue3_1", 110 },
{ "skyblue2", 111 },
{ "chartreuse2_1", 112 },
{ "darkolivegreen3_1", 113 },
{ "palegreen3_1", 114 },
{ "darkseagreen3", 115 },
{ "darkslategray3", 116 },
{ "skyblue1", 117 },
{ "chartreuse1", 118 },
{ "lightgreen", 119 },
{ "lightgreen_1", 120 },
{ "palegreen1", 121 },
{ "aquamarine1_1", 122 },
{ "darkslategray1", 123 },
{ "red3", 124 },
{ "deeppink4_2", 125 },
{ "mediumvioletred", 126 },
{ "magenta3", 127 },
{ "darkviolet_1", 128 },
{ "purple_2", 129 },
{ "darkorange3", 130 },
{ "indianred", 131 },
{ "hotpink3", 132 },
{ "mediumorchid3", 133 },
{ "mediumorchid", 134 },
{ "mediumpurple2", 135 },
{ "darkgoldenrod", 136 },
{ "lightsalmon3", 137 },
{ "rosybrown", 138 },
{ "grey63", 139 },
{ "mediumpurple2_1", 140 },
{ "mediumpurple1", 141 },
{ "gold3", 142 },
{ "darkkhaki", 143 },
{ "navajowhite3", 144 },
{ "grey69", 145 },
{ "lightsteelblue3", 146 },
{ "lightsteelblue", 147 },
{ "yellow3", 148 },
{ "darkolivegreen3_2", 149 },
{ "darkseagreen3_1", 150 },
{ "darkseagreen2", 151 },
{ "lightcyan3", 152 },
{ "lightskyblue1", 153 },
{ "greenyellow", 154 },
{ "darkolivegreen2", 155 },
{ "palegreen1_1", 156 },
{ "darkseagreen2_1", 157 },
{ "darkseagreen1", 158 },
{ "paleturquoise1", 159 },
{ "red3_1", 160 },
{ "deeppink3", 161 },
{ "deeppink3_1", 162 },
{ "magenta3_1", 163 },
{ "magenta3_2", 164 },
{ "magenta2", 165 },
{ "darkorange3_1", 166 },
{ "indianred_1", 167 },
{ "hotpink3_1", 168 },
{ "hotpink2", 169 },
{ "orchid", 170 },
{ "mediumorchid1", 171 },
{ "orange3", 172 },
{ "lightsalmon3_1", 173 },
{ "lightpink3", 174 },
{ "pink3", 175 },
{ "plum3", 176 },
{ "violet", 177 },
{ "gold3_1", 178 },
{ "lightgoldenrod3", 179 },
{ "tan", 180 },
{ "mistyrose3", 181 },
{ "thistle3", 182 },
{ "plum2", 183 },
{ "yellow3_1", 184 },
{ "khaki3", 185 },
{ "lightgoldenrod2", 186 },
{ "lightyellow3", 187 },
{ "grey84", 188 },
{ "lightsteelblue1", 189 },
{ "yellow2", 190 },
{ "darkolivegreen1", 191 },
{ "darkolivegreen1_1", 192 },
{ "darkseagreen1_1", 193 },
{ "honeydew2", 194 },
{ "lightcyan1", 195 },
{ "red1", 196 },
{ "deeppink2", 197 },
{ "deeppink1", 198 },
{ "deeppink1_1", 199 },
{ "magenta2_1", 200 },
{ "magenta1", 201 },
{ "orangered1", 202 },
{ "indianred1", 203 },
{ "indianred1_1", 204 },
{ "hotpink", 205 },
{ "hotpink_1", 206 },
{ "mediumorchid1_1", 207 },
{ "darkorange", 208 },
{ "salmon1", 209 },
{ "lightcoral", 210 },
{ "palevioletred1", 211 },
{ "orchid2", 212 },
{ "orchid1", 213 },
{ "orange1", 214 },
{ "sandybrown", 215 },
{ "lightsalmon1", 216 },
{ "lightpink1", 217 },
{ "pink1", 218 },
{ "plum1", 219 },
{ "gold1", 220 },
{ "lightgoldenrod2_1", 221 },
{ "lightgoldenrod2_2", 222 },
{ "navajowhite1", 223 },
{ "mistyrose1", 224 },
{ "thistle1", 225 },
{ "yellow1", 226 },
{ "lightgoldenrod1", 227 },
{ "khaki1", 228 },
{ "wheat1", 229 },
{ "cornsilk1", 230 },
{ "grey100", 231 },
{ "grey3", 232 },
{ "grey7", 233 },
{ "grey11", 234 },
{ "grey15", 235 },
{ "grey19", 236 },
{ "grey23", 237 },
{ "grey27", 238 },
{ "grey30", 239 },
{ "grey35", 240 },
{ "grey39", 241 },
{ "grey42", 242 },
{ "grey46", 243 },
{ "grey50", 244 },
{ "grey54", 245 },
{ "grey58", 246 },
{ "grey62", 247 },
{ "grey66", 248 },
{ "grey70", 249 },
{ "grey74", 250 },
{ "grey78", 251 },
{ "grey82", 252 },
{ "grey85", 253 },
{ "grey89", 254 },
{ "grey93", 255 },
};
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static partial class ColorTable
{
private static readonly Dictionary<int, string> _nameLookup;
private static readonly Dictionary<string, int> _numberLookup;
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
static ColorTable()
{
_numberLookup = GenerateTable();
_nameLookup = new Dictionary<int, string>();
foreach (var pair in _numberLookup)
{
_nameLookup.Add(pair.Value, pair.Key);
}
}
public static Color GetColor(int number)
{
if (number < 0 || number > 255)
{
throw new InvalidOperationException("Color number must be between 0 and 255");
}
return ColorPalette.EightBit[number];
}
public static Color? GetColor(string name)
{
if (!_numberLookup.TryGetValue(name, out var number))
{
return null;
}
if (number > ColorPalette.EightBit.Count - 1)
{
return null;
}
return ColorPalette.EightBit[number];
}
public static string? GetName(int number)
{
_nameLookup.TryGetValue(number, out var name);
return name;
}
}
}

View File

@@ -1,73 +0,0 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class Composer : IRenderable
{
private readonly BlockElement _root;
/// <inheritdoc/>
public int Length => _root.Length;
public Composer()
{
_root = new BlockElement();
}
public static Composer New()
{
return new Composer();
}
public Composer Text(string text)
{
_root.Append(new TextElement(text));
return this;
}
public Composer Foreground(Color color, Action<Composer> action)
{
if (action is null)
{
return this;
}
var content = new Composer();
action(content);
_root.Append(new ForegroundElement(color, content));
return this;
}
public Composer Background(Color color, Action<Composer> action)
{
if (action is null)
{
return this;
}
var content = new Composer();
action(content);
_root.Append(new BackgroundElement(color, content));
return this;
}
public Composer Style(Styles style, Action<Composer> action)
{
if (action is null)
{
return this;
}
var content = new Composer();
action(content);
_root.Append(new StyleElement(style, content));
return this;
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
_root.Render(renderer);
}
}
}

View File

@@ -1,35 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class BackgroundElement : IRenderable
{
private readonly Color _color;
private readonly IRenderable _element;
/// <inheritdoc/>
public int Length => _element.Length;
public BackgroundElement(Color color, IRenderable element)
{
_color = color;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
if (renderer is null)
{
throw new ArgumentNullException(nameof(renderer));
}
using (renderer.PushColor(_color, foreground: false))
{
_element.Render(renderer);
}
}
}
}

View File

@@ -1,41 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class BlockElement : IRenderable
{
private readonly List<IRenderable> _elements;
/// <inheritdoc/>
public int Length { get; private set; }
public IReadOnlyList<IRenderable> Elements => _elements;
public BlockElement()
{
_elements = new List<IRenderable>();
}
public BlockElement Append(IRenderable element)
{
if (element != null)
{
_elements.Add(element);
Length += element.Length;
}
return this;
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
foreach (var element in _elements)
{
element.Render(renderer);
}
}
}
}

View File

@@ -1,35 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class ForegroundElement : IRenderable
{
private readonly Color _color;
private readonly IRenderable _element;
/// <inheritdoc/>
public int Length => _element.Length;
public ForegroundElement(Color color, IRenderable element)
{
_color = color;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
if (renderer is null)
{
throw new ArgumentNullException(nameof(renderer));
}
using (renderer.PushColor(_color, foreground: true))
{
_element.Render(renderer);
}
}
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class LineBreakElement : IRenderable
{
/// <inheritdoc/>
public int Length => 0;
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
renderer.Write(Environment.NewLine);
}
}
}

View File

@@ -1,35 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class StyleElement : IRenderable
{
private readonly Styles _style;
private readonly IRenderable _element;
/// <inheritdoc/>
public int Length => _element.Length;
public StyleElement(Styles style, IRenderable element)
{
_style = style;
_element = element ?? throw new ArgumentNullException(nameof(element));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
if (renderer is null)
{
throw new ArgumentNullException(nameof(renderer));
}
using (renderer.PushStyle(_style))
{
_element.Render(renderer);
}
}
}
}

View File

@@ -1,24 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
internal sealed class TextElement : IRenderable
{
private readonly string _text;
/// <inheritdoc/>
public int Length => _text.Length;
public TextElement(string text)
{
_text = text ?? throw new System.ArgumentNullException(nameof(text));
}
/// <inheritdoc/>
public void Render(IAnsiConsole renderer)
{
renderer.Write(_text);
}
}
}

View File

@@ -1,19 +0,0 @@
namespace Spectre.Console.Internal
{
/// <summary>
/// Represents something that can be rendered to a console.
/// </summary>
internal interface IRenderable
{
/// <summary>
/// Gets the length of the element.
/// </summary>
int Length { get; }
/// <summary>
/// Renders the element using the specified renderer.
/// </summary>
/// <param name="console">The renderer to use.</param>
void Render(IAnsiConsole console);
}
}

View File

@@ -1,7 +1,7 @@
using System;
using Spectre.Console.Internal;
using System.Runtime.InteropServices;
namespace Spectre.Console
namespace Spectre.Console.Internal
{
internal static class ConsoleBuilder
{
@@ -14,9 +14,41 @@ namespace Spectre.Console
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)
@@ -24,13 +56,13 @@ namespace Spectre.Console
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);
}
}
}

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

View File

@@ -5,6 +5,25 @@ namespace Spectre.Console.Internal
{
internal static class ConsoleExtensions
{
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var current = new Style(console.Foreground, console.Background, console.Decoration);
console.SetColor(style.Foreground, true);
console.SetColor(style.Background, false);
console.Decoration = style.Decoration;
return new StyleScope(console, current);
}
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
{
if (console is null)
@@ -12,21 +31,21 @@ namespace Spectre.Console.Internal
throw new ArgumentNullException(nameof(console));
}
var current = console.Foreground;
var current = foreground ? console.Foreground : console.Background;
console.SetColor(color, foreground);
return new ColorScope(console, current, foreground);
}
public static IDisposable PushStyle(this IAnsiConsole console, Styles style)
public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = console.Style;
console.Style = style;
return new StyleScope(console, current);
var current = console.Decoration;
console.Decoration = decoration;
return new DecorationScope(console, current);
}
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
@@ -47,6 +66,33 @@ namespace Spectre.Console.Internal
}
}
internal sealed class StyleScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Style _style;
public StyleScope(IAnsiConsole console, Style style)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_style = style ?? throw new ArgumentNullException(nameof(style));
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~StyleScope()
{
throw new InvalidOperationException("Style scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.SetColor(_style.Foreground, true);
_console.SetColor(_style.Background, false);
_console.Decoration = _style.Decoration;
}
}
internal sealed class ColorScope : IDisposable
{
private readonly IAnsiConsole _console;
@@ -74,28 +120,28 @@ namespace Spectre.Console.Internal
}
}
internal sealed class StyleScope : IDisposable
internal sealed class DecorationScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Styles _style;
private readonly Decoration _decoration;
public StyleScope(IAnsiConsole console, Styles color)
public DecorationScope(IAnsiConsole console, Decoration decoration)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_style = color;
_decoration = decoration;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~StyleScope()
~DecorationScope()
{
throw new InvalidOperationException("Style scope was not disposed.");
throw new InvalidOperationException("Decoration scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.Style = _style;
_console.Decoration = _decoration;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Internal
{
internal static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
public static bool AnyTrue(this IEnumerable<bool> source)
{
return source.Any(b => b);
}
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
return Enumerate(source.GetEnumerator());
}
public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerator<T> source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source));
}
var first = true;
var last = !source.MoveNext();
T current;
for (var index = 0; !last; index++)
{
current = source.Current;
last = !source.MoveNext();
yield return (index, first, last, current);
first = false;
}
}
public static IEnumerable<TResult> SelectIndex<T, TResult>(this IEnumerable<T> source, Func<T, int, TResult> func)
{
return source.Select((value, index) => func(value, index));
}
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
this IEnumerable<TFirst> source, IEnumerable<TSecond> first)
{
return source.Zip(first, (first, second) => (first, second));
}
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
{
return first.Zip(second, (a, b) => (a, b))
.Zip(third, (a, b) => (a.a, a.b, b));
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Spectre.Console.Internal
{
internal static class StringExtensions
{
// Cache whether or not internally normalized line endings
// already are normalized. No reason to do yet another replace if it is.
private static readonly bool _alreadyNormalized
= Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase);
public static int CellLength(this string text, Encoding encoding)
{
return Cell.GetCellLength(encoding, text);
}
public static string NormalizeLineEndings(this string text, bool native = false)
{
text ??= string.Empty;
var normalized = text?.Replace("\r\n", "\n")?.Replace("\r", string.Empty) ?? string.Empty;
if (native && !_alreadyNormalized)
{
normalized = normalized.Replace("\n", Environment.NewLine);
}
return normalized;
}
public static string[] SplitLines(this string text)
{
var result = text?.NormalizeLineEndings()?.Split(new[] { '\n' }, StringSplitOptions.None);
return result ?? Array.Empty<string>();
}
public static string[] SplitWords(this string word, StringSplitOptions options = StringSplitOptions.None)
{
var result = new List<string>();
static string Read(StringBuffer reader, Func<char, bool> criteria)
{
var buffer = new StringBuilder();
while (!reader.Eof)
{
var current = reader.Peek();
if (!criteria(current))
{
break;
}
buffer.Append(reader.Read());
}
return buffer.ToString();
}
using (var reader = new StringBuffer(word))
{
while (!reader.Eof)
{
var current = reader.Peek();
if (char.IsWhiteSpace(current))
{
var x = Read(reader, c => char.IsWhiteSpace(c));
if (options != StringSplitOptions.RemoveEmptyEntries)
{
result.Add(x);
}
}
else
{
result.Add(Read(reader, c => !char.IsWhiteSpace(c)));
}
}
}
return result.ToArray();
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class StyleExtensions
{
public static Style Combine(this Style style, IEnumerable<Style> source)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
if (source is null)
{
return style;
}
var current = style;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text;
namespace Spectre.Console.Internal
{
@@ -13,7 +14,9 @@ namespace Spectre.Console.Internal
private ConsoleColor _foreground;
private ConsoleColor _background;
public AnsiConsoleCapabilities Capabilities { get; }
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public int Width
{
@@ -41,7 +44,7 @@ namespace Spectre.Console.Internal
}
}
public Styles Style { get; set; }
public Decoration Decoration { get; set; }
public Color Foreground
{
@@ -82,28 +85,32 @@ namespace Spectre.Console.Internal
}
}
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system)
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
{
_out = @out;
_system = system;
Capabilities = new AnsiConsoleCapabilities(false, _system);
if (_out.IsStandardOut())
{
_defaultForeground = System.Console.ForegroundColor;
_defaultBackground = System.Console.BackgroundColor;
Encoding = System.Console.OutputEncoding;
}
else
{
_defaultForeground = ConsoleColor.Gray;
_defaultBackground = ConsoleColor.Black;
Encoding = Encoding.UTF8;
}
Capabilities = new Capabilities(false, _system, legacyConsole);
}
public void Write(string text)
{
_out.Write(text);
_out.Write(text.NormalizeLineEndings(native: true));
}
}
}

View File

@@ -0,0 +1,155 @@
// Taken and modified from NStack project by Miguel de Icaza, licensed under BSD-3
// https://github.com/migueldeicaza/NStack/blob/3fc024fb2c2e99927d3e12991570fb54db8ce01e/NStack/unicode/Rune.ColumnWidth.cs
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
namespace Spectre.Console.Internal
{
internal static class Cell
{
[SuppressMessage("Design", "RCS1169:Make field read-only.")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces for multi-line statements should not share line")]
private static readonly uint[,] _combining = new uint[,]
{
{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
{ 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
{ 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
{ 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
{ 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
{ 0xE0100, 0xE01EF },
};
public static int GetCellLength(Encoding encoding, string text)
{
return text.Sum(c => GetCellLength(encoding, c));
}
public static int GetCellLength(Encoding encoding, char rune)
{
if (rune == '\r' || rune == '\n')
{
return 0;
}
// Is it represented by a single byte?
// In that case we don't have to calculate the
// actual cell width.
if (encoding.GetByteCount(new[] { rune }) == 1)
{
return 1;
}
var irune = (uint)rune;
if (irune < 32)
{
return 0;
}
if (irune < 127)
{
return 1;
}
if (irune >= 0x7f && irune <= 0xa0)
{
return 0;
}
// Binary search in table of non-spacing characters
if (BinarySearch(irune, _combining, _combining.GetLength(0) - 1) != 0)
{
return 0;
}
// If we arrive here, ucs is not a combining or C0/C1 control character
return 1 +
((irune >= 0x1100 &&
(irune <= 0x115f || /* Hangul Jamo init. consonants */
irune == 0x2329 || irune == 0x232a ||
(irune >= 0x2e80 && irune <= 0xa4cf &&
irune != 0x303f) || /* CJK ... Yi */
(irune >= 0xac00 && irune <= 0xd7a3) || /* Hangul Syllables */
(irune >= 0xf900 && irune <= 0xfaff) || /* CJK Compatibility Ideographs */
(irune >= 0xfe10 && irune <= 0xfe19) || /* Vertical forms */
(irune >= 0xfe30 && irune <= 0xfe6f) || /* CJK Compatibility Forms */
(irune >= 0xff00 && irune <= 0xff60) || /* Fullwidth Forms */
(irune >= 0xffe0 && irune <= 0xffe6) ||
(irune >= 0x20000 && irune <= 0x2fffd) ||
(irune >= 0x30000 && irune <= 0x3fffd))) ? 1 : 0);
}
private static int BinarySearch(uint rune, uint[,] table, int max)
{
var min = 0;
int mid;
if (rune < table[0, 0] || rune > table[max, 1])
{
return 0;
}
while (max >= min)
{
mid = (min + max) / 2;
if (rune > table[mid, 1])
{
min = mid + 1;
}
else if (rune < table[mid, 0])
{
max = mid - 1;
}
else
{
return 1;
}
}
return 0;
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Internal
{
internal static class MarkupParser
{
public static Text Parse(string text, Style? style = null)
{
style ??= Style.Plain;
var result = new Text();
using var tokenizer = new MarkupTokenizer(text);
var stack = new Stack<Style>();
while (tokenizer.MoveNext())
{
var token = tokenizer.Current;
if (token == null)
{
break;
}
if (token.Kind == MarkupTokenKind.Open)
{
var parsedStyle = StyleParser.Parse(token.Value);
stack.Push(parsedStyle);
}
else if (token.Kind == MarkupTokenKind.Close)
{
if (stack.Count == 0)
{
throw new InvalidOperationException($"Encountered closing tag when none was expected near position {token.Position}.");
}
stack.Pop();
}
else if (token.Kind == MarkupTokenKind.Text)
{
// Get the effecive style.
var effectiveStyle = style.Combine(stack.Reverse());
result.Append(token.Value, effectiveStyle);
}
else
{
throw new InvalidOperationException("Encountered unkown markup token.");
}
}
if (stack.Count > 0)
{
throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?");
}
return result;
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class MarkupToken
{
public MarkupTokenKind Kind { get; }
public string Value { get; }
public int Position { get; set; }
public MarkupToken(MarkupTokenKind kind, string value, int position)
{
Kind = kind;
Value = value ?? throw new ArgumentNullException(nameof(value));
Position = position;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Spectre.Console.Internal
{
internal enum MarkupTokenKind
{
Text = 0,
Open,
Close,
}
}

View File

@@ -0,0 +1,110 @@
using System;
using System.Text;
namespace Spectre.Console.Internal
{
internal sealed class MarkupTokenizer : IDisposable
{
private readonly StringBuffer _reader;
public MarkupToken? Current { get; private set; }
public MarkupTokenizer(string text)
{
_reader = new StringBuffer(text ?? throw new ArgumentNullException(nameof(text)));
}
public void Dispose()
{
_reader.Dispose();
}
public bool MoveNext()
{
if (_reader.Eof)
{
return false;
}
var current = _reader.Peek();
if (current == '[')
{
var position = _reader.Position;
_reader.Read();
if (_reader.Eof)
{
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
}
current = _reader.Peek();
if (current == '[')
{
_reader.Read();
Current = new MarkupToken(MarkupTokenKind.Text, "[", position);
return true;
}
if (current == '/')
{
_reader.Read();
if (_reader.Eof)
{
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
}
current = _reader.Peek();
if (current != ']')
{
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
}
_reader.Read();
Current = new MarkupToken(MarkupTokenKind.Close, string.Empty, position);
return true;
}
var builder = new StringBuilder();
while (!_reader.Eof)
{
current = _reader.Peek();
if (current == ']')
{
break;
}
builder.Append(_reader.Read());
}
if (_reader.Eof)
{
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
}
_reader.Read();
Current = new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position);
return true;
}
else
{
var position = _reader.Position;
var builder = new StringBuilder();
while (!_reader.Eof)
{
current = _reader.Peek();
if (current == '[')
{
break;
}
builder.Append(_reader.Read());
}
Current = new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position);
return true;
}
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace Spectre.Console.Internal
{
internal sealed class StringBuffer : IDisposable
{
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")]
private readonly StringReader _reader;
private readonly int _length;
public int Position { get; private set; }
public bool Eof => Position >= _length;
public StringBuffer(string text)
{
text ??= string.Empty;
_reader = new StringReader(text);
_length = text.Length;
Position = 0;
}
public void Dispose()
{
_reader.Dispose();
}
public char Peek()
{
if (Eof)
{
throw new InvalidOperationException("Tried to peek past the end of the text.");
}
return (char)_reader.Peek();
}
public char Read()
{
if (Eof)
{
throw new InvalidOperationException("Tried to read past the end of the text.");
}
Position++;
return (char)_reader.Read();
}
}
}

View File

@@ -0,0 +1,199 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Spectre.Console.Internal
{
internal static class StyleParser
{
public static Style Parse(string text)
{
var style = Parse(text, out var error);
if (error != null)
{
throw new InvalidOperationException(error);
}
if (style == null)
{
// This should not happen, but we need to please the compiler
// which cannot know that style isn't null here.
throw new InvalidOperationException("Could not parse style.");
}
return style;
}
public static bool TryParse(string text, out Style? style)
{
style = Parse(text, out var error);
return error == null;
}
private static Style? Parse(string text, out string? error)
{
var effectiveDecoration = (Decoration?)null;
var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)null;
var parts = text.Split(new[] { ' ' });
var foreground = true;
foreach (var part in parts)
{
if (part.Equals("default", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
{
foreground = false;
continue;
}
var decoration = DecorationTable.GetDecoration(part);
if (decoration != null)
{
if (effectiveDecoration == null)
{
effectiveDecoration = Decoration.None;
}
effectiveDecoration |= decoration.Value;
}
else
{
var color = ColorTable.GetColor(part);
if (color == null)
{
if (part.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
color = ParseHexColor(part, out error);
if (!string.IsNullOrWhiteSpace(error))
{
return null;
}
}
else if (part.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
{
color = ParseRgbColor(part, out error);
if (!string.IsNullOrWhiteSpace(error))
{
return null;
}
}
else
{
error = !foreground
? $"Could not find color '{part}'."
: $"Could not find color or style '{part}'.";
return null;
}
}
if (foreground)
{
if (effectiveForeground != null)
{
error = "A foreground color has already been set.";
return null;
}
effectiveForeground = color;
}
else
{
if (effectiveBackground != null)
{
error = "A background color has already been set.";
return null;
}
effectiveBackground = color;
}
}
}
error = null;
return new Style(effectiveForeground, effectiveBackground, effectiveDecoration);
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
private static Color? ParseHexColor(string hex, out string? error)
{
error = null;
hex ??= string.Empty;
hex = hex.Replace("#", string.Empty).Trim();
try
{
if (!string.IsNullOrWhiteSpace(hex))
{
if (hex.Length == 6)
{
return new Color(
(byte)Convert.ToUInt32(hex.Substring(0, 2), 16),
(byte)Convert.ToUInt32(hex.Substring(2, 2), 16),
(byte)Convert.ToUInt32(hex.Substring(4, 2), 16));
}
else if (hex.Length == 3)
{
return new Color(
(byte)Convert.ToUInt32(new string(hex[0], 2), 16),
(byte)Convert.ToUInt32(new string(hex[1], 2), 16),
(byte)Convert.ToUInt32(new string(hex[2], 2), 16));
}
}
}
catch (Exception ex)
{
error = $"Invalid hex color '#{hex}'. {ex.Message}";
return null;
}
error = $"Invalid hex color '#{hex}'.";
return null;
}
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
private static Color? ParseRgbColor(string rgb, out string? error)
{
try
{
error = null;
var normalized = rgb ?? string.Empty;
if (normalized.Length >= 3)
{
// Trim parenthesises
normalized = normalized.Substring(3).Trim();
if (normalized.StartsWith("(", StringComparison.OrdinalIgnoreCase) &&
normalized.EndsWith(")", StringComparison.OrdinalIgnoreCase))
{
normalized = normalized.Trim('(').Trim(')');
var parts = normalized.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 3)
{
return new Color(
(byte)Convert.ToInt32(parts[0], CultureInfo.InvariantCulture),
(byte)Convert.ToInt32(parts[1], CultureInfo.InvariantCulture),
(byte)Convert.ToInt32(parts[2], CultureInfo.InvariantCulture));
}
}
}
}
catch (Exception ex)
{
error = $"Invalid RGB color '{rgb}'. {ex.Message}";
return null;
}
error = $"Invalid RGB color '{rgb}'.";
return null;
}
}
}

View File

@@ -0,0 +1,75 @@
// Ported from Rich by Will McGugan, licensed under MIT.
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/_ratio.py
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Spectre.Console.Internal
{
internal static class Ratio
{
public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values)
{
ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList();
var totalRatio = ratios.Sum();
if (totalRatio <= 0)
{
return values;
}
var totalRemaining = total;
var result = new List<int>();
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
{
if (ratio != 0 && totalRatio > 0)
{
var distributed = (int)Math.Min(maximum, Math.Round((double)(ratio * totalRemaining / totalRatio)));
result.Add(value - distributed);
totalRemaining -= distributed;
totalRatio -= ratio;
}
else
{
result.Add(value);
}
}
return result;
}
public static List<int> Distribute(int total, List<int> ratios, List<int>? minimums = null)
{
if (minimums != null)
{
ratios = ratios.Zip(minimums, (a, b) => (ratio: a, min: b)).Select(a => a.min > 0 ? a.ratio : 0).ToList();
}
var totalRatio = ratios.Sum();
Debug.Assert(totalRatio > 0, "Sum or ratios must be > 0");
var totalRemaining = total;
var distributedTotal = new List<int>();
if (minimums == null)
{
minimums = ratios.Select(_ => 0).ToList();
}
foreach (var (ratio, minimum) in ratios.Zip(minimums, (a, b) => (a, b)))
{
var distributed = (totalRatio > 0)
? Math.Max(minimum, (int)Math.Ceiling(ratio * totalRemaining / (double)totalRatio))
: totalRemaining;
distributedTotal.Add(distributed);
totalRatio -= ratio;
totalRemaining -= distributed;
}
return distributedTotal;
}
}
}

View File

@@ -0,0 +1,61 @@
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAlignable"/>.
/// </summary>
public static class AlignableExtensions
{
/// <summary>
/// Sets the alignment for an <see cref="IAlignable"/> object.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <param name="alignment">The alignment.</param>
/// <returns>The same alignable object.</returns>
public static T WithAlignment<T>(this T alignable, Justify alignment)
where T : IAlignable
{
alignable.Alignment = alignment;
return alignable;
}
/// <summary>
/// Sets the <see cref="IAlignable"/> object to be left aligned.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <returns>The same alignable object.</returns>
public static T LeftAligned<T>(this T alignable)
where T : IAlignable
{
alignable.Alignment = Justify.Left;
return alignable;
}
/// <summary>
/// Sets the <see cref="IAlignable"/> object to be centered.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <returns>The same alignable object.</returns>
public static T Centered<T>(this T alignable)
where T : IAlignable
{
alignable.Alignment = Justify.Center;
return alignable;
}
/// <summary>
/// Sets the <see cref="IAlignable"/> object to be right aligned.
/// </summary>
/// <typeparam name="T">The alignable type.</typeparam>
/// <param name="alignable">The alignable object.</param>
/// <returns>The same alignable object.</returns>
public static T RightAligned<T>(this T alignable)
where T : IAlignable
{
alignable.Alignment = Justify.Right;
return alignable;
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Spectre.Console.Rendering
{
/// <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);
}
}

Some files were not shown because too many files have changed in this diff Show More