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