Compare commits

...

21 Commits

Author SHA1 Message Date
Patrik Svensson
ae6d2c63a3 Add column support
Adds support for rendering arbitrary data into columns.

Closes #67
2020-09-05 07:45:38 +02:00
Patrik Svensson
e946289bd9 Make styles composable
Also adds some new extension methods and make some APIs a bit more consistent.

Closes #64
2020-09-03 21:26:20 +02:00
Patrik Svensson
bcaaa6b2d3 Update table of contents (skip-ci) 2020-09-03 20:04:06 +02:00
Patrik Svensson
d3588a4b06 Remove styles and colors (skip-ci) 2020-09-03 20:03:26 +02:00
Patrik Svensson
7471e9d38c Add panel header support
Closes #63
2020-09-03 19:02:29 +02:00
Patrik Svensson
9f8ca6d648 Add text overflow support
Closes #61
2020-09-03 14:57:57 +02:00
Patrik Svensson
88edfe68ec Add border for color representations 2020-09-02 09:55:44 +02:00
Patrik Svensson
5d32764a64 Fix typo: SRG -> SGR 2020-09-02 09:02:59 +02:00
Patrik Svensson
4f6c9c62c7 Set fetch depth to 0 2020-09-01 22:05:46 +02:00
Patrik Svensson
f2677213a4 Separate build and run when publishing docs 2020-09-01 22:00:44 +02:00
Patrik Svensson
bdaf00a556 Remove empty documentation pages 2020-09-01 21:51:25 +02:00
Patrik Svensson
7de4b6c7b9 Build docs as part of CI 2020-09-01 21:50:36 +02:00
Patrik Svensson
0bc801e3eb Build docs when source change 2020-09-01 21:40:49 +02:00
Patrik Svensson
88a82cdad0 Build docs in release mode 2020-09-01 21:39:40 +02:00
Patrik Svensson
0cecb555d5 Show documentation version in footer 2020-09-01 21:33:15 +02:00
Patrik Svensson
52e3ee17b0 Fix logo link 2020-09-01 21:28:52 +02:00
Dave Glick
caf7661e66 Moves all the pages up a level and hardcodes the section name 2020-09-01 21:23:01 +02:00
Dave Glick
65f0a085cc Updated to the latest Statiq Web with breakage fixes 2020-09-01 21:03:47 +02:00
Patrik Svensson
a123806cd8 Add docs for escaping [ and ] in markup 2020-09-01 16:52:40 +02:00
Patrik Svensson
173645cdd2 Added documentation for markup text 2020-08-31 14:05:28 +02:00
Patrik Svensson
7fd2efaeb5 Merge segments before rendering
This will reduce the number of segments to render
and produce cleaner ANSI escape code sequences.

Closes #46
2020-08-30 12:40:34 +02:00
61 changed files with 1494 additions and 540 deletions

View File

@@ -6,6 +6,35 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs: jobs:
###################################################
# DOCS
###################################################
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: '3.1.301' # SDK Version to use.
- name: Build
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd docs
dotnet run --configuration Release
###################################################
# BUILD
###################################################
build: build:
name: Build name: Build
if: "!contains(github.event.head_commit.message, 'skip-ci')" if: "!contains(github.event.head_commit.message, 'skip-ci')"

View File

@@ -4,14 +4,22 @@ on:
push: push:
paths: paths:
- 'docs/**' - 'docs/**'
- 'src/**'
jobs: jobs:
###################################################
# DOCS
###################################################
build: build:
name: Deploy name: Deploy
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@master uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
@@ -24,4 +32,4 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
cd docs cd docs
dotnet run -- deploy dotnet run --configuration Release -- deploy

View File

@@ -6,6 +6,8 @@ on:
- '*' - '*'
branches: branches:
- main - main
paths:
- 'src/**'
env: env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
@@ -13,12 +15,37 @@ env:
jobs: jobs:
###################################################
# DOCS
###################################################
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: '3.1.301' # SDK Version to use.
- name: Build
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cd docs
dotnet run --configuration Release
################################################### ###################################################
# BUILD # BUILD
################################################### ###################################################
build: build:
name: Build name: Build
needs: [docs]
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')" if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
strategy: strategy:
matrix: matrix:

281
README.md
View File

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

View File

@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<DefaultItemExcludes>$(DefaultItemExcludes);output\**;.gitignore</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);output\**;.gitignore</DefaultItemExcludes>
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -22,8 +23,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Statiq.Web" Version="1.0.0-alpha.9" /> <PackageReference Include="Statiq.Web" Version="1.0.0-beta.5" />
<PackageReference Include="NJsonSchema" Version="10.1.12" /> <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" />
</ItemGroup> </ItemGroup>
<Target Name="Versioning" BeforeTargets="MinVer">
<PropertyGroup Label="Build">
<MinVerDefaultPreReleasePhase>preview</MinVerDefaultPreReleasePhase>
<MinVerVerbosity>normal</MinVerVerbosity>
</PropertyGroup>
</Target>
</Project> </Project>

1
docs/Preview.ps1 Normal file
View File

@@ -0,0 +1 @@
dotnet run -- preview --virtual-dir "spectre.console"

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -29,7 +29,7 @@
<nav id="topnav" class="navbar navbar-expand-lg navbar-light"> <nav id="topnav" class="navbar navbar-expand-lg navbar-light">
<div class="container py-3"> <div class="container py-3">
<a class="navbar-brand" href="/spectre.console/docs"><img id="logo" src="/spectre.console/assets/logo.svg" alt="Spectre.Console"> Spectre.Console</a> <a class="navbar-brand" href="/spectre.console"><img id="logo" src="/spectre.console/assets/logo.svg" alt="Spectre.Console"> Spectre.Console</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@@ -52,8 +52,8 @@
{ {
@RenderSection(Constants.Sections.Splash, false) @RenderSection(Constants.Sections.Splash, false)
} }
@{ @{
string section = Document.Destination.Segments.Length > 1 ? Document.Destination.Segments[0].ToString() : null; string section = "docs";
} }
<div class="flex-grow-1 d-flex bg-body flex-column @(section != null ? "section-" + section : null)"> <div class="flex-grow-1 d-flex bg-body flex-column @(section != null ? "section-" + section : null)">
@@ -62,7 +62,7 @@
<div id="titlebar" class="py-4"> <div id="titlebar" class="py-4">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
@{ @{
string titleBarClasses = Document.GetBool(Constants.NoSidebar) ? string.Empty : "offset-md-3 offset-lg-2"; string titleBarClasses = Document.GetBool(Constants.NoSidebar) ? string.Empty : "offset-md-3 offset-lg-2";
} }
<div class="@titleBarClasses px-3 px-md-0"> <div class="@titleBarClasses px-3 px-md-0">
@@ -97,7 +97,7 @@
</div> </div>
</div> </div>
} }
<div class="flex-grow-1 d-flex flex-column bg-body"> <div class="flex-grow-1 d-flex flex-column bg-body">
@if (Document.GetBool(Constants.NoContainer)) @if (Document.GetBool(Constants.NoContainer))
{ {
@@ -126,21 +126,22 @@
} }
else else
{ {
IDocument root = Outputs[nameof(Content)].First(x => x.Destination == section + "/index.html"); IDocument root = OutputPages["index.html"].First();
<div class="sidebar-nav-item @(Document.IdEquals(root) ? "active" : null)"> <div class="sidebar-nav-item @(Document.IdEquals(root) ? "active" : null)">
@Html.DocumentLink(root) @Html.DocumentLink(root)
</div> </div>
@foreach (IDocument document in root.GetChildren().OnlyVisible()) @foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible())
{ {
<div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(document.HasChildren() ? "has-children" : null)"> DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document);
<div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)">
@Html.DocumentLink(document) @Html.DocumentLink(document)
</div> </div>
@if (document.HasVisibleChildren()) @if (documentChildren.OnlyVisible().Any())
{ {
<div class="sidebar-nav-children @(Document.IdEquals(document) || document.GetChildren().Any(x => Document.IdEquals(x)) ? "active" : null)"> <div class="sidebar-nav-children @(Document.IdEquals(document) || documentChildren.Any(x => Document.IdEquals(x)) ? "active" : null)">
@foreach (IDocument child in document.GetChildren().OnlyVisible()) @foreach (IDocument child in documentChildren.OnlyVisible())
{ {
<div class="sidebar-nav-child @(Document.IdEquals(child) ? "active" : null)"> <div class="sidebar-nav-child @(Document.IdEquals(child) ? "active" : null)">
@Html.DocumentLink(child) @Html.DocumentLink(child)
@@ -182,7 +183,10 @@
</div> </div>
<div id="footer" class="p-3 text-white font-size-sm"> <div id="footer" class="p-3 text-white font-size-sm">
<div class="container"> <div class="container">
<div>© @DateTime.Today.Year Spectre Systems AB</div> <div>
<span>© @DateTime.Today.Year Spectre Systems AB</span>
<span class="float-right" style="color: #888888;">@VersionUtilities.GetVersion()</span>
</div>
</div> </div>
</div> </div>
<script> <script>
@@ -195,7 +199,7 @@
}, },
startOnLoad: false, startOnLoad: false,
cloneCssStyles: false cloneCssStyles: false
}); });
mermaid.init(undefined, ".mermaid"); mermaid.init(undefined, ".mermaid");
// Remove the max-width setting that Mermaid sets // Remove the max-width setting that Mermaid sets
@@ -214,13 +218,13 @@
center: true, center: true,
maxZoom: 20, maxZoom: 20,
zoomScaleSensitivity: 0.6 zoomScaleSensitivity: 0.6
}); });
// Do the reset once right away to fit the diagram // Do the reset once right away to fit the diagram
panZoom.resize(); panZoom.resize();
panZoom.fit(); panZoom.fit();
panZoom.center(); panZoom.center();
$(window).resize(function(){ $(window).resize(function(){
panZoom.resize(); panZoom.resize();
panZoom.fit(); panZoom.fit();

View File

@@ -3,5 +3,6 @@
@using Statiq.Web @using Statiq.Web
@using Statiq.Web.Pipelines @using Statiq.Web.Pipelines
@using Docs @using Docs
@using Docs.Utilities;
@inherits StatiqRazorPage<IDocument> @inherits StatiqRazorPage<IDocument>

View File

@@ -18,6 +18,7 @@ $thebackground: $gray-200;
display: inline-block; display: inline-block;
width: 60px; width: 60px;
height: 15px; height: 15px;
border: 2px solid #000000;
} }
#topnav { #topnav {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +1,5 @@
Title: Colors Title: Colors
Order: 2 Order: 4
--- ---
The following is a list of the standard 8-bit colors supported in terminals. The following is a list of the standard 8-bit colors supported in terminals.

View File

@@ -1,6 +0,0 @@
Title: Console API
Order: 2
Hidden: True
---
__To be written__

View File

@@ -1,6 +0,0 @@
Title: Grids
Order: 4
Hidden: True
---
__To be written__

View File

@@ -1,6 +0,0 @@
Title: Panels
Order: 5
Hidden: True
---
__To be written__

View File

@@ -1,6 +0,0 @@
Title: Tables
Order: 3
Hidden: True
---
__To be written__

View File

@@ -1,15 +0,0 @@
Title: Start
NoContainer: true
---
@section Splash {
<div id="hero" class="jumbotron jumbotron-fluid mb-0">
<div class="container">
<div class="display-4 text-white">Spectre.Console</div>
<p class="lead text-white">
A .NET Standard library that makes it easier to create beautiful console applications.
<br /><br />
<a class="btn btn-primary" href="/spectre.console/docs" role="button">Take me to the documentation</a>
</p>
</div>
</div>
}

View File

@@ -13,7 +13,7 @@ for Python written by Will McGugan.
* Supports tables, grids, panels, and a [Rich](https://github.com/willmcgugan/rich) * Supports tables, grids, panels, and a [Rich](https://github.com/willmcgugan/rich)
inspired markup language. inspired markup language.
* Supports the most common * Supports the most common
[SRG parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters) [SGR parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters)
when it comes to text styling such as bold, dim, italic, underline, strikethrough, when it comes to text styling such as bold, dim, italic, underline, strikethrough,
and blinking text. and blinking text.
* Supports `3`/`4`/`8`/`24`-bit colors in the terminal. * Supports `3`/`4`/`8`/`24`-bit colors in the terminal.

104
docs/input/markup.md Normal file
View File

@@ -0,0 +1,104 @@
Title: Markup
Order: 3
Hidden: False
---
In `Spectre.Console` there's a class called `Markup` that
allows you to output rich text to the console.
```csharp
AnsiConsole.Render(new Markup("[bold yellow]Hello[/] [red]World![/]"));
```
Which should output something similar to the image below. Note that the
actual appearance might vary depending on your terminal.
![](/spectre.console/assets/images/helloworld.png)
The `Markup` class implements `IRenderable` which means that you
can use this in tables, grids, and panels. Most classes that support
rendering of `IRenderable` also have overloads for rendering rich text.
```csharp
var table = new Table();
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
table.AddColumn(new TableColumn("[blue]Bar[/]"));
```
# Convenience methods
There is also convenience methods on `AnsiConsole` that can be used
to write markup text to the console without instantiating a new `Markup`
instance.
```csharp
AnsiConsole.Markup("[underline green]Hello[/] ");
AnsiConsole.MarkupLine("[bold]World[/]");
```
# Escaping format characters
To output a `[` you use `[[`, and to output a `]` you use `]]`.
```csharp
AnsiConsole.Markup("[[Hello]] "); // [Hello]
AnsiConsole.Markup("[red][[World]][/]"); // [World]
```
# Setting background color
You can set the background color in markup by prefixing the color with
`on`.
```
[bold yellow on blue]Hello[/]
[default on blue]World[/]
```
# Colors
For a list of colors, see the [Colors](xref:colors) section.
# Styles
Note that what styles that can be used is defined by the system or your terminal software, and may not appear as they should.
<table class="table">
<tr>
<td><code>bold</code></td>
<td>Bold text</td>
</tr>
<tr>
<td><code>dim</code></td>
<td>Dim or faint text</td>
</tr>
<tr>
<td><code>italic</code></td>
<td>Italic text</td>
</tr>
<tr>
<td><code>underline</code></td>
<td>Underlined text</td>
</tr>
<tr>
<td><code>invert</code></td>
<td>Swaps the foreground and background colors</td>
</tr>
<tr>
<td><code>conceal</code></td>
<td>Hides the text</td>
</tr>
<tr>
<td><code>slowblink</code></td>
<td>Makes text blink slowly</td>
</tr>
<tr>
<td><code>rapidblink</code></td>
<td>Makes text blink</td>
</tr>
<tr>
<td><code>strikethrough</code></td>
<td>Shows text with a horizontal line through the center</td>
</tr>
</table>

View File

@@ -11,15 +11,6 @@ namespace Docs
return document?.GetString(Constants.Description, string.Empty) ?? string.Empty; return document?.GetString(Constants.Description, string.Empty) ?? string.Empty;
} }
public static bool HasVisibleChildren(this IDocument document)
{
if (document != null)
{
return document.HasChildren() && document.GetChildren().Any(x => x.IsVisible());
}
return false;
}
public static bool IsVisible(this IDocument document) public static bool IsVisible(this IDocument document)
{ {
return !document.GetBool(Constants.Hidden, false); return !document.GetBool(Constants.Hidden, false);

View File

@@ -1,8 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Docs.Models; using Docs.Models;
using NJsonSchema;
using Statiq.Common; using Statiq.Common;
using Statiq.Core; using Statiq.Core;

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NJsonSchema;
using Statiq.Common; using Statiq.Common;
using System.Xml.Linq; using System.Xml.Linq;
using Docs.Pipelines; using Docs.Pipelines;

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
namespace Docs.Utilities
{
public static class VersionUtilities
{
public static string GetVersion()
{
return GetVersion(typeof(VersionUtilities).Assembly);
}
private static string GetVersion(Assembly assembly)
{
if (assembly == null)
{
return "?";
}
try
{
var info = FileVersionInfo.GetVersionInfo(assembly.Location);
return info.ProductVersion ?? "?";
}
catch
{
return "?";
}
}
}
}

View File

@@ -4,7 +4,7 @@ namespace ColorExample
{ {
public static class Program public static class Program
{ {
public static void Main(string[] args) public static void Main()
{ {
if (AnsiConsole.Capabilities.ColorSystem == ColorSystem.NoColors) if (AnsiConsole.Capabilities.ColorSystem == ColorSystem.NoColors)
{ {

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Spectre.Console;
using Spectre.Console.Rendering;
namespace ColumnsExample
{
public static class Program
{
public static async Task Main()
{
// Download some random users
using var client = new HttpClient();
dynamic users = JObject.Parse(
await client.GetStringAsync("https://randomuser.me/api/?results=15"));
// Create a card for each user
var cards = new List<Panel>();
foreach(var user in users.results)
{
cards.Add(new Panel(GetCard(user))
.SetHeader($"{user.location.country}")
.RoundedBorder().Expand());
}
// Render all cards in columns
AnsiConsole.Render(new Columns(cards));
}
private static string GetCard(dynamic user)
{
var name = $"{user.name.first} {user.name.last}";
var country = $"{user.location.city}";
return $"[b]{name}[/]\n[yellow]{country}[/]";
}
}
}

View File

@@ -1,11 +1,10 @@
using System;
using Spectre.Console; using Spectre.Console;
namespace Diagnostic namespace Diagnostic
{ {
public class Program public static class Program
{ {
public static void Main(string[] args) public static void Main()
{ {
AnsiConsole.MarkupLine("Color system: [bold]{0}[/]", AnsiConsole.Capabilities.ColorSystem); AnsiConsole.MarkupLine("Color system: [bold]{0}[/]", AnsiConsole.Capabilities.ColorSystem);
AnsiConsole.MarkupLine("Supports ansi? [bold]{0}[/]", AnsiConsole.Capabilities.SupportsAnsi); AnsiConsole.MarkupLine("Supports ansi? [bold]{0}[/]", AnsiConsole.Capabilities.SupportsAnsi);

View File

@@ -2,9 +2,9 @@ using Spectre.Console;
namespace GridExample namespace GridExample
{ {
public sealed class Program public static class Program
{ {
static void Main(string[] args) public static void Main()
{ {
AnsiConsole.WriteLine(); AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options]] [[[[--]] <additional arguments>...]]]][/]"); AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options]] [[[[--]] <additional arguments>...]]]][/]");

View File

@@ -1,10 +1,11 @@
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering;
namespace PanelExample namespace PanelExample
{ {
class Program public static class Program
{ {
static void Main(string[] args) public static void Main()
{ {
var content = new Markup( var content = new Markup(
"[underline]I[/] heard [underline on blue]you[/] like panels\n\n\n\n" + "[underline]I[/] heard [underline on blue]you[/] like panels\n\n\n\n" +
@@ -13,32 +14,28 @@ namespace PanelExample
AnsiConsole.Render( AnsiConsole.Render(
new Panel( new Panel(
new Panel(content) new Panel(content)
{ .SetBorderKind(BorderKind.Rounded)));
Border = BorderKind.Rounded
}));
// Left adjusted panel with text // Left adjusted panel with text
AnsiConsole.Render(new Panel( AnsiConsole.Render(
new Text("Left adjusted\nLeft").LeftAligned()) new Panel(new Text("Left adjusted\nLeft").LeftAligned())
{ .Expand()
Expand = true, .SquareBorder()
}); .SetHeader("Left", Style.WithForeground(Color.Red)));
// Centered ASCII panel with text // Centered ASCII panel with text
AnsiConsole.Render(new Panel( AnsiConsole.Render(
new Text("Centered\nCenter").Centered()) new Panel(new Text("Centered\nCenter").Centered())
{ .Expand()
Expand = true, .AsciiBorder()
Border = BorderKind.Ascii, .SetHeader("Center", Style.WithForeground(Color.Green), Justify.Center));
});
// Right adjusted, rounded panel with text // Right adjusted, rounded panel with text
AnsiConsole.Render(new Panel( AnsiConsole.Render(
new Text("Right adjusted\nRight").RightAligned()) new Panel(new Text("Right adjusted\nRight").RightAligned())
{ .Expand()
Expand = true, .RoundedBorder()
Border = BorderKind.Rounded, .SetHeader("Right", Style.WithForeground(Color.Blue), Justify.Right));
});
} }
} }
} }

View File

@@ -1,11 +1,10 @@
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering;
namespace TableExample namespace TableExample
{ {
public static class Program public static class Program
{ {
public static void Main(string[] args) public static void Main()
{ {
// A simple table // A simple table
RenderSimpleTable(); RenderSimpleTable();
@@ -36,7 +35,7 @@ namespace TableExample
private static void RenderBigTable() private static void RenderBigTable()
{ {
// Create the table. // Create the table.
var table = new Table().SetBorder(BorderKind.Rounded); var table = new Table().SetBorderKind(BorderKind.Rounded);
table.AddColumn("[red underline]Foo[/]"); table.AddColumn("[red underline]Foo[/]");
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true }); table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true });
@@ -58,7 +57,7 @@ namespace TableExample
private static void RenderNestedTable() private static void RenderNestedTable()
{ {
// Create simple table. // Create simple table.
var simple = new Table().SetBorder(BorderKind.Rounded).SetBorderColor(Color.Red); var simple = new Table().SetBorderKind(BorderKind.Rounded).SetBorderColor(Color.Red);
simple.AddColumn(new TableColumn("[u]Foo[/]").Centered()); simple.AddColumn(new TableColumn("[u]Foo[/]").Centered());
simple.AddColumn(new TableColumn("[u]Bar[/]")); simple.AddColumn(new TableColumn("[u]Bar[/]"));
simple.AddColumn(new TableColumn("[u]Baz[/]")); simple.AddColumn(new TableColumn("[u]Baz[/]"));
@@ -67,7 +66,7 @@ namespace TableExample
simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
// Create other table. // Create other table.
var second = new Table().SetBorder(BorderKind.Square).SetBorderColor(Color.Green); var second = new Table().SetBorderKind(BorderKind.Square).SetBorderColor(Color.Green);
second.AddColumn(new TableColumn("[u]Foo[/]")); second.AddColumn(new TableColumn("[u]Foo[/]"));
second.AddColumn(new TableColumn("[u]Bar[/]")); second.AddColumn(new TableColumn("[u]Bar[/]"));
second.AddColumn(new TableColumn("[u]Baz[/]")); second.AddColumn(new TableColumn("[u]Baz[/]"));
@@ -75,7 +74,7 @@ namespace TableExample
second.AddRow(simple, new Text("Whaaat"), new Text("Lolz")); second.AddRow(simple, new Text("Whaaat"), new Text("Lolz"));
second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", "");
var table = new Table().SetBorder(BorderKind.Rounded); var table = new Table().SetBorderKind(BorderKind.Rounded);
table.AddColumn(new TableColumn(new Panel("[u]Foo[/]").SetBorderColor(Color.Red))); table.AddColumn(new TableColumn(new Panel("[u]Foo[/]").SetBorderColor(Color.Red)));
table.AddColumn(new TableColumn(new Panel("[u]Bar[/]").SetBorderColor(Color.Green))); table.AddColumn(new TableColumn(new Panel("[u]Bar[/]").SetBorderColor(Color.Green)));
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]").SetBorderColor(Color.Blue))); table.AddColumn(new TableColumn(new Panel("[u]Baz[/]").SetBorderColor(Color.Blue)));

View File

@@ -82,5 +82,8 @@ dotnet_diagnostic.RCS1079.severity = warning
# RCS1057: Add empty line between declarations. # RCS1057: Add empty line between declarations.
dotnet_diagnostic.RCS1057.severity = none dotnet_diagnostic.RCS1057.severity = none
# RCS1057: Validate arguments correctly
dotnet_diagnostic.RCS1227.severity = none
# IDE0004: Remove Unnecessary Cast # IDE0004: Remove Unnecessary Cast
dotnet_diagnostic.IDE0004.severity = warning dotnet_diagnostic.IDE0004.severity = warning

View File

@@ -12,7 +12,7 @@ namespace Spectre.Console.Tests.Unit
{ {
[Theory] [Theory]
[InlineData("[yellow]Hello[/]", "Hello")] [InlineData("[yellow]Hello[/]", "Hello")]
[InlineData("[yellow]Hello [italic]World[/]![/]", "Hello World!")] [InlineData("[yellow]Hello [italic]World[/]![/]", "Hello World!")]
public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected) public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected)
{ {
// Given // Given
@@ -26,7 +26,7 @@ namespace Spectre.Console.Tests.Unit
} }
[Theory] [Theory]
[InlineData("[yellow]Hello [[ World[/]", "Hello [ World")] [InlineData("[yellow]Hello [[ World[/]", "Hello [ World")]
public void Should_Be_Able_To_Escape_Tags(string markup, string expected) public void Should_Be_Able_To_Escape_Tags(string markup, string expected)
{ {
// Given // Given

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class ColumnsTests
{
private sealed class User
{
public string Name { get; set; }
public string Country { get; set; }
}
[Fact]
public void Should_Render_Columns_Correctly()
{
// Given
var console = new PlainConsole(width: 61);
var users = new[]
{
new User { Name = "Savannah Thompson", Country = "Australia" },
new User { Name = "Sophie Ramos", Country = "United States" },
new User { Name = "Katrin Goldberg", Country = "Germany" },
};
var cards = new List<Panel>();
foreach (var user in users)
{
cards.Add(
new Panel($"[b]{user.Name}[/]\n[yellow]{user.Country}[/]")
.RoundedBorder().Expand());
}
// When
console.Render(new Columns(cards));
// Then
console.Lines.Count.ShouldBe(4);
console.Lines[0].ShouldBe("╭────────────────────╮ ╭────────────────╮ ╭─────────────────╮");
console.Lines[1].ShouldBe("│ Savannah Thompson │ │ Sophie Ramos │ │ Katrin Goldberg │");
console.Lines[2].ShouldBe("│ Australia │ │ United States │ │ Germany │");
console.Lines[3].ShouldBe("╰────────────────────╯ ╰────────────────╯ ╰─────────────────╯");
}
}
}

View File

@@ -42,7 +42,7 @@ namespace Spectre.Console.Tests.Unit
} }
[Fact] [Fact]
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns() public void Should_Add_Empty_Items_If_User_Provides_Less_Row_Items_Than_Columns()
{ {
// Given // Given
var grid = new Grid(); var grid = new Grid();
@@ -50,11 +50,10 @@ namespace Spectre.Console.Tests.Unit
grid.AddColumn(); grid.AddColumn();
// When // When
var result = Record.Exception(() => grid.AddRow("Foo")); grid.AddRow("Foo");
// Then // Then
result.ShouldBeOfType<InvalidOperationException>(); grid.RowCount.ShouldBe(1);
result.Message.ShouldBe("The number of row columns are less than the number of grid columns.");
} }
[Fact] [Fact]

View File

@@ -40,6 +40,108 @@ namespace Spectre.Console.Tests.Unit
console.Lines[2].ShouldBe("└───────────────────┘"); console.Lines[2].ShouldBe("└───────────────────┘");
} }
[Fact]
public void Should_Render_Panel_With_Header()
{
// Given
var console = new PlainConsole(width: 80);
// When
console.Render(new Panel("Hello World")
{
Header = new Header("Greeting"),
Expand = true,
Padding = new Padding(2, 2),
});
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("┌─Greeting─────────────────────────────────────────────────────────────────────┐");
console.Lines[1].ShouldBe("│ Hello World │");
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
}
[Fact]
public void Should_Render_Panel_With_Left_Aligned_Header()
{
// Given
var console = new PlainConsole(width: 80);
// When
console.Render(new Panel("Hello World")
{
Header = new Header("Greeting").LeftAligned(),
Expand = true,
});
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("┌─Greeting─────────────────────────────────────────────────────────────────────┐");
console.Lines[1].ShouldBe("│ Hello World │");
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
}
[Fact]
public void Should_Render_Panel_With_Centered_Header()
{
// Given
var console = new PlainConsole(width: 80);
// When
console.Render(new Panel("Hello World")
{
Header = new Header("Greeting").Centered(),
Expand = true,
});
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("┌───────────────────────────────────Greeting───────────────────────────────────┐");
console.Lines[1].ShouldBe("│ Hello World │");
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
}
[Fact]
public void Should_Render_Panel_With_Right_Aligned_Header()
{
// Given
var console = new PlainConsole(width: 80);
// When
console.Render(new Panel("Hello World")
{
Header = new Header("Greeting").RightAligned(),
Expand = true,
});
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("┌─────────────────────────────────────────────────────────────────────Greeting─┐");
console.Lines[1].ShouldBe("│ Hello World │");
console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘");
}
[Fact]
public void Should_Collapse_Header_If_It_Will_Not_Fit()
{
// Given
var console = new PlainConsole(width: 10);
// When
console.Render(new Panel("Hello World")
{
Header = new Header("Greeting"),
Expand = true,
});
// Then
console.Lines.Count.ShouldBe(4);
console.Lines[0].ShouldBe("┌─Greet…─┐");
console.Lines[1].ShouldBe("│ Hello │");
console.Lines[2].ShouldBe("│ World │");
console.Lines[3].ShouldBe("└────────┘");
}
[Fact] [Fact]
public void Should_Render_Panel_With_Unicode_Correctly() public void Should_Render_Panel_With_Unicode_Correctly()
{ {

View File

@@ -87,7 +87,7 @@ namespace Spectre.Console.Tests.Unit
} }
[Fact] [Fact]
public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns() public void Should_Add_Empty_Items_If_User_Provides_Less_Row_Items_Than_Columns()
{ {
// Given // Given
var table = new Table(); var table = new Table();
@@ -95,11 +95,10 @@ namespace Spectre.Console.Tests.Unit
table.AddColumn("World"); table.AddColumn("World");
// When // When
var result = Record.Exception(() => table.AddRow("Foo")); table.AddRow("Foo");
// Then // Then
result.ShouldBeOfType<InvalidOperationException>(); table.RowCount.ShouldBe(1);
result.Message.ShouldBe("The number of row columns are less than the number of table columns.");
} }
[Fact] [Fact]
@@ -174,7 +173,7 @@ namespace Spectre.Console.Tests.Unit
{ {
// A simple table // A simple table
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var table = new Table() { Border = BorderKind.Rounded }; var table = new Table() { BorderKind = BorderKind.Rounded };
table.AddColumn("Foo"); table.AddColumn("Foo");
table.AddColumn("Bar"); table.AddColumn("Bar");
table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right }); table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right });
@@ -184,7 +183,7 @@ namespace Spectre.Console.Tests.Unit
// Render a table in some panels. // Render a table in some panels.
console.Render(new Panel(new Panel(table) console.Render(new Panel(new Panel(table)
{ {
Border = BorderKind.Ascii, BorderKind = BorderKind.Ascii,
})); }));
// Then // Then
@@ -256,7 +255,7 @@ namespace Spectre.Console.Tests.Unit
{ {
// Given // Given
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var table = new Table { Border = BorderKind.Ascii }; var table = new Table { BorderKind = BorderKind.Ascii };
table.AddColumns("Foo", "Bar", "Baz"); table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred"); table.AddRow("Grault", "Garply", "Fred");
@@ -279,7 +278,7 @@ namespace Spectre.Console.Tests.Unit
{ {
// Given // Given
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var table = new Table { Border = BorderKind.Rounded }; var table = new Table { BorderKind = BorderKind.Rounded };
table.AddColumns("Foo", "Bar", "Baz"); table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred"); table.AddRow("Grault", "Garply", "Fred");
@@ -302,7 +301,7 @@ namespace Spectre.Console.Tests.Unit
{ {
// Given // Given
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var table = new Table { Border = BorderKind.None }; var table = new Table { BorderKind = BorderKind.None };
table.AddColumns("Foo", "Bar", "Baz"); table.AddColumns("Foo", "Bar", "Baz");
table.AddRow("Qux", "Corgi", "Waldo"); table.AddRow("Qux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred"); table.AddRow("Grault", "Garply", "Fred");

View File

@@ -65,6 +65,21 @@ namespace Spectre.Console.Tests.Unit
fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n"); fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n");
} }
[Fact]
public void Should_Render_Panel_2()
{
// Given
var console = new PlainConsole(width: 80);
// When
console.Render(new Markup("[b]Hello World[/]\n[yellow]Hello World[/]"));
// Then
console.Lines.Count.ShouldBe(2);
console.Lines[0].ShouldBe("Hello World");
console.Lines[1].ShouldBe("Hello World");
}
[Theory] [Theory]
[InlineData(5, "Hello World", "Hello\nWorld")] [InlineData(5, "Hello World", "Hello\nWorld")]
[InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")] [InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")]
@@ -83,5 +98,25 @@ namespace Spectre.Console.Tests.Unit
.NormalizeLineEndings() .NormalizeLineEndings()
.ShouldBe(expected); .ShouldBe(expected);
} }
[Theory]
[InlineData(Overflow.Fold, "foo \npneumonoultram\nicroscopicsili\ncovolcanoconio\nsis bar qux")]
[InlineData(Overflow.Crop, "foo \npneumonoultram\nbar qux")]
[InlineData(Overflow.Ellipsis, "foo \npneumonoultra…\nbar qux")]
public void Should_Overflow_Text_Correctly(Overflow overflow, string expected)
{
// Given
var fixture = new PlainConsole(14);
var text = new Text("foo pneumonoultramicroscopicsilicovolcanoconiosis bar qux")
.SetOverflow(overflow);
// When
fixture.Render(text);
// Then
fixture.Output
.NormalizeLineEndings()
.ShouldBe(expected);
}
} }
} }

View File

@@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Colors", "..\examples\Color
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostic", "..\examples\Diagnostic\Diagnostic.csproj", "{4337F255-88E9-4408-81A3-DF1AF58AC753}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostic", "..\examples\Diagnostic\Diagnostic.csproj", "{4337F255-88E9-4408-81A3-DF1AF58AC753}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Columns", "..\examples\Columns\Columns.csproj", "{33357599-C79D-4299-888F-634E2C3EACEF}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -121,6 +123,18 @@ Global
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x64.Build.0 = Release|Any CPU {4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x64.Build.0 = Release|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x86.ActiveCfg = Release|Any CPU {4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x86.ActiveCfg = Release|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x86.Build.0 = Release|Any CPU {4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x86.Build.0 = Release|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Debug|x64.ActiveCfg = Debug|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Debug|x64.Build.0 = Debug|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Debug|x86.ActiveCfg = Debug|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Debug|x86.Build.0 = Debug|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Release|Any CPU.Build.0 = Release|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Release|x64.ActiveCfg = Release|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Release|x64.Build.0 = Release|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Release|x86.ActiveCfg = Release|Any CPU
{33357599-C79D-4299-888F-634E2C3EACEF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -131,6 +145,7 @@ Global
{C7FF6FDB-FB59-4517-8669-521C96AB7323} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {C7FF6FDB-FB59-4517-8669-521C96AB7323} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{1F51C55C-BA4C-4856-9001-0F7924FFB179} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {1F51C55C-BA4C-4856-9001-0F7924FFB179} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{4337F255-88E9-4408-81A3-DF1AF58AC753} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {4337F255-88E9-4408-81A3-DF1AF58AC753} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{33357599-C79D-4299-888F-634E2C3EACEF} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Spectre.Console.Internal; using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
@@ -30,8 +31,11 @@ namespace Spectre.Console
using (console.PushStyle(Style.Plain)) using (console.PushStyle(Style.Plain))
{ {
var segments = renderable.Render(options, console.Width).Where(x => !(x.Text.Length == 0 && !x.IsLineBreak)).ToArray();
segments = Segment.Merge(segments).ToArray();
var current = Style.Plain; var current = Style.Plain;
foreach (var segment in renderable.Render(options, console.Width)) foreach (var segment in segments)
{ {
if (string.IsNullOrEmpty(segment.Text)) if (string.IsNullOrEmpty(segment.Text))
{ {

View File

@@ -1,5 +1,4 @@
using System.IO; using System.IO;
using System.Runtime.InteropServices;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// Renders things in columns.
/// </summary>
public sealed class Columns : Renderable, IPaddable, IExpandable
{
private readonly List<IRenderable> _items;
/// <inheritdoc/>
public Padding Padding { get; set; } = new Padding(0, 1);
/// <summary>
/// Gets or sets a value indicating whether or not the columns should
/// expand to the available space. If <c>false</c>, the column
/// width will be auto calculated. Defaults to <c>true</c>.
/// </summary>
public bool Expand { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="Columns"/> class.
/// </summary>
/// <param name="items">The items to render.</param>
public Columns(IEnumerable<IRenderable> items)
{
if (items is null)
{
throw new ArgumentNullException(nameof(items));
}
_items = new List<IRenderable>(items);
}
/// <summary>
/// Initializes a new instance of the <see cref="Columns"/> class.
/// </summary>
/// <param name="items">The items to render.</param>
public Columns(IEnumerable<string> items)
{
if (items is null)
{
throw new ArgumentNullException(nameof(items));
}
_items = new List<IRenderable>(items.Select(item => new Markup(item)));
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var maxPadding = Math.Max(Padding.Left, Padding.Right);
var itemWidths = _items.Select(item => item.Measure(context, maxWidth).Max).ToArray();
var columnCount = CalculateColumnCount(maxWidth, itemWidths, _items.Count, maxPadding);
var table = new Table();
table.NoBorder();
table.HideHeaders();
table.PadRightCell = false;
if (Expand)
{
table.Expand();
}
// Add columns
for (var index = 0; index < columnCount; index++)
{
table.AddColumn(new TableColumn(string.Empty)
{
Padding = Padding,
NoWrap = true,
});
}
// Add rows
for (var start = 0; start < _items.Count; start += columnCount)
{
table.AddRow(_items.Skip(start).Take(columnCount).ToArray());
}
return ((IRenderable)table).Render(context, maxWidth);
}
// Algorithm borrowed from https://github.com/willmcgugan/rich/blob/master/rich/columns.py
private int CalculateColumnCount(int maxWidth, int[] itemWidths, int columnCount, int padding)
{
var widths = new Dictionary<int, int>();
while (columnCount > 1)
{
var columnIndex = 0;
widths.Clear();
var exceededTotalWidth = false;
foreach (var renderableWidth in IterateWidths(itemWidths, columnCount))
{
widths[columnIndex] = Math.Max(widths.ContainsKey(columnIndex) ? widths[columnIndex] : 0, renderableWidth);
var totalWidth = widths.Values.Sum() + (padding * (widths.Count - 1));
if (totalWidth > maxWidth)
{
columnCount = widths.Count - 1;
exceededTotalWidth = true;
break;
}
else
{
columnIndex = (columnIndex + 1) % columnCount;
}
}
if (!exceededTotalWidth)
{
break;
}
}
return columnCount;
}
private IEnumerable<int> IterateWidths(int[] itemWidths, int columnCount)
{
foreach (var width in itemWidths)
{
yield return width;
}
if (_items.Count % columnCount != 0)
{
for (var i = 0; i < columnCount - (_items.Count % columnCount) - 1; i++)
{
yield return 0;
}
}
}
}
}

View File

@@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@@ -9,10 +7,27 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable grid. /// A renderable grid.
/// </summary> /// </summary>
public sealed class Grid : Renderable public sealed class Grid : Renderable, IExpandable
{ {
private readonly Table _table; private readonly Table _table;
/// <summary>
/// Gets the number of columns in the table.
/// </summary>
public int ColumnCount => _table.ColumnCount;
/// <summary>
/// Gets the number of rows in the table.
/// </summary>
public int RowCount => _table.RowCount;
/// <inheritdoc/>
public bool Expand
{
get => _table.Expand;
set => _table.Expand = value;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Grid"/> class. /// Initializes a new instance of the <see cref="Grid"/> class.
/// </summary> /// </summary>
@@ -20,7 +35,7 @@ namespace Spectre.Console
{ {
_table = new Table _table = new Table
{ {
Border = BorderKind.None, BorderKind = BorderKind.None,
ShowHeaders = false, ShowHeaders = false,
IsGrid = true, IsGrid = true,
PadRightCell = false, PadRightCell = false,
@@ -75,45 +90,6 @@ namespace Spectre.Console
}); });
} }
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="count">The number of columns to add.</param>
public void AddColumns(int count)
{
for (var index = 0; index < count; index++)
{
AddColumn(new GridColumn());
}
}
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="columns">The columns to add.</param>
public void AddColumns(params GridColumn[] columns)
{
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
foreach (var column in columns)
{
AddColumn(column);
}
}
/// <summary>
/// Adds an empty row to the grid.
/// </summary>
public void AddEmptyRow()
{
var columns = new IRenderable[_table.ColumnCount];
Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = Text.Empty);
AddRow(columns);
}
/// <summary> /// <summary>
/// Adds a new row to the grid. /// Adds a new row to the grid.
/// </summary> /// </summary>
@@ -125,11 +101,6 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(columns)); 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) if (columns.Length > _table.ColumnCount)
{ {
throw new InvalidOperationException("The number of row columns are greater than the number of grid columns."); throw new InvalidOperationException("The number of row columns are greater than the number of grid columns.");

View File

@@ -1,5 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
@@ -8,6 +10,63 @@ namespace Spectre.Console
/// </summary> /// </summary>
public static class GridExtensions public static class GridExtensions
{ {
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="grid">The grid to add the column to.</param>
/// <param name="count">The number of columns to add.</param>
public static void AddColumns(this Grid grid, int count)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
for (var index = 0; index < count; index++)
{
grid.AddColumn(new GridColumn());
}
}
/// <summary>
/// Adds a column to the grid.
/// </summary>
/// <param name="grid">The grid to add the column to.</param>
/// <param name="columns">The columns to add.</param>
public static void AddColumns(this Grid grid, params GridColumn[] columns)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
if (columns is null)
{
throw new ArgumentNullException(nameof(columns));
}
foreach (var column in columns)
{
grid.AddColumn(column);
}
}
/// <summary>
/// Adds an empty row to the grid.
/// </summary>
/// <param name="grid">The grid to add the row to.</param>
public static void AddEmptyRow(this Grid grid)
{
if (grid is null)
{
throw new ArgumentNullException(nameof(grid));
}
var columns = new IRenderable[grid.ColumnCount];
Enumerable.Range(0, grid.ColumnCount).ForEach(index => columns[index] = Text.Empty);
grid.AddRow(columns);
}
/// <summary> /// <summary>
/// Adds a new row to the grid. /// Adds a new row to the grid.
/// </summary> /// </summary>

View File

@@ -0,0 +1,60 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents a header.
/// </summary>
public sealed class Header : IAlignable
{
/// <summary>
/// Gets the header text.
/// </summary>
public string Text { get; }
/// <summary>
/// Gets or sets the header style.
/// </summary>
public Style? Style { get; set; }
/// <summary>
/// Gets or sets the header alignment.
/// </summary>
public Justify? Alignment { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Header"/> class.
/// </summary>
/// <param name="text">The header text.</param>
/// <param name="style">The header style.</param>
/// <param name="alignment">The header alignment.</param>
public Header(string text, Style? style = null, Justify? alignment = null)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
Style = style;
Alignment = alignment;
}
/// <summary>
/// Sets the header style.
/// </summary>
/// <param name="style">The header style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public Header SetStyle(Style? style)
{
Style = style ?? Style.Plain;
return this;
}
/// <summary>
/// Sets the header alignment.
/// </summary>
/// <param name="alignment">The header alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public Header SetAlignment(Justify alignment)
{
Alignment = alignment;
return this;
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// A renderable piece of markup text. /// A renderable piece of markup text.
/// </summary> /// </summary>
public sealed class Markup : Renderable, IAlignable public sealed class Markup : Renderable, IAlignable, IOverflowable
{ {
private readonly Paragraph _paragraph; private readonly Paragraph _paragraph;
@@ -18,6 +18,13 @@ namespace Spectre.Console
set => _paragraph.Alignment = value; set => _paragraph.Alignment = value;
} }
/// <inheritdoc/>
public Overflow? Overflow
{
get => _paragraph.Overflow;
set => _paragraph.Overflow = value;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Markup"/> class. /// Initializes a new instance of the <see cref="Markup"/> class.
/// </summary> /// </summary>

View File

@@ -0,0 +1,24 @@
namespace Spectre.Console
{
/// <summary>
/// Represents text overflow.
/// </summary>
public enum Overflow
{
/// <summary>
/// Put any excess characters on the next line.
/// </summary>
Fold = 0,
/// <summary>
/// Truncates the text at the end of the line.
/// </summary>
Crop = 1,
/// <summary>
/// Truncates the text at the end of the line and
/// also inserts an ellipsis character.
/// </summary>
Ellipsis = 2,
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
@@ -15,13 +16,13 @@ namespace Spectre.Console
private readonly IRenderable _child; private readonly IRenderable _child;
/// <inheritdoc/> /// <inheritdoc/>
public BorderKind Border { get; set; } = BorderKind.Square; public BorderKind BorderKind { get; set; } = BorderKind.Square;
/// <inheritdoc/> /// <inheritdoc/>
public bool SafeBorder { get; set; } = true; public bool SafeBorder { get; set; } = true;
/// <inheritdoc/> /// <inheritdoc/>
public Color? BorderColor { get; set; } public Style? BorderStyle { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not the panel should /// Gets or sets a value indicating whether or not the panel should
@@ -35,6 +36,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
public Padding Padding { get; set; } = new Padding(1, 1); public Padding Padding { get; set; } = new Padding(1, 1);
/// <summary>
/// Gets or sets the header.
/// </summary>
public Header? Header { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class. /// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary> /// </summary>
@@ -58,15 +64,15 @@ namespace Spectre.Console
{ {
var childWidth = _child.Measure(context, maxWidth); var childWidth = _child.Measure(context, maxWidth);
return new Measurement( return new Measurement(
childWidth.Min + 2 + Padding.GetHorizontalPadding(), childWidth.Min + EdgeWidth + Padding.GetHorizontalPadding(),
childWidth.Max + 2 + Padding.GetHorizontalPadding()); childWidth.Max + EdgeWidth + Padding.GetHorizontalPadding());
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{ {
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); var border = SpectreBorder.GetBorder(BorderKind, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null); var borderStyle = BorderStyle ?? Style.Plain;
var paddingWidth = Padding.GetHorizontalPadding(); var paddingWidth = Padding.GetHorizontalPadding();
var childWidth = maxWidth - EdgeWidth - paddingWidth; var childWidth = maxWidth - EdgeWidth - paddingWidth;
@@ -77,21 +83,16 @@ namespace Spectre.Console
childWidth = measurement.Max; childWidth = measurement.Max;
} }
var panelWidth = childWidth + paddingWidth; var panelWidth = childWidth + EdgeWidth + paddingWidth;
panelWidth = Math.Min(panelWidth, maxWidth);
var result = new List<Segment>();
// Panel top // Panel top
var result = new List<Segment> AddTopBorder(result, context, border, borderStyle, panelWidth);
{
new Segment(border.GetPart(BorderPart.HeaderTopLeft), borderStyle),
new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth), borderStyle),
new Segment(border.GetPart(BorderPart.HeaderTopRight), borderStyle),
Segment.LineBreak,
};
// Render the child.
var childSegments = _child.Render(context, childWidth);
// Split the child segments into lines. // Split the child segments into lines.
var childSegments = _child.Render(context, childWidth);
foreach (var line in Segment.SplitLines(childSegments, panelWidth)) foreach (var line in Segment.SplitLines(childSegments, panelWidth))
{ {
result.Add(new Segment(border.GetPart(BorderPart.CellLeft), borderStyle)); result.Add(new Segment(border.GetPart(BorderPart.CellLeft), borderStyle));
@@ -126,12 +127,62 @@ namespace Spectre.Console
} }
// Panel bottom // Panel bottom
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft), borderStyle)); AddBottomBorder(result, border, borderStyle, panelWidth);
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight), borderStyle));
result.Add(Segment.LineBreak);
return result; return result;
} }
private static void AddBottomBorder(List<Segment> result, SpectreBorder border, Style borderStyle, int panelWidth)
{
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth - EdgeWidth), borderStyle));
result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight), borderStyle));
result.Add(Segment.LineBreak);
}
private void AddTopBorder(List<Segment> segments, RenderContext context, SpectreBorder border, Style borderStyle, int panelWidth)
{
segments.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft), borderStyle));
if (Header != null)
{
var leftSpacing = 0;
var rightSpacing = 0;
var headerWidth = panelWidth - (EdgeWidth * 2);
var header = Segment.TruncateWithEllipsis(Header.Text, Header.Style ?? borderStyle, context.Encoding, headerWidth);
var excessWidth = headerWidth - header.CellLength(context.Encoding);
if (excessWidth > 0)
{
switch (Header.Alignment ?? Justify.Left)
{
case Justify.Left:
leftSpacing = 0;
rightSpacing = excessWidth;
break;
case Justify.Right:
leftSpacing = excessWidth;
rightSpacing = 0;
break;
case Justify.Center:
leftSpacing = excessWidth / 2;
rightSpacing = (excessWidth / 2) + (excessWidth % 2);
break;
}
}
segments.Add(new Segment(border.GetPart(BorderPart.HeaderTop, leftSpacing + 1), borderStyle));
segments.Add(header);
segments.Add(new Segment(border.GetPart(BorderPart.HeaderTop, rightSpacing + 1), borderStyle));
}
else
{
segments.Add(new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth - EdgeWidth), borderStyle));
}
segments.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight), borderStyle));
segments.Add(Segment.LineBreak);
}
} }
} }

View File

@@ -0,0 +1,50 @@
using System;
namespace Spectre.Console.Rendering
{
/// <summary>
/// Contains extension methods for <see cref="Panel"/>.
/// </summary>
public static class PanelExtensions
{
/// <summary>
/// Sets the panel header.
/// </summary>
/// <param name="panel">The panel.</param>
/// <param name="text">The header text.</param>
/// <param name="style">The header style.</param>
/// <param name="alignment">The header alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Panel SetHeader(this Panel panel, string text, Style? style = null, Justify? alignment = null)
{
if (panel is null)
{
throw new ArgumentNullException(nameof(panel));
}
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
return SetHeader(panel, new Header(text, style, alignment));
}
/// <summary>
/// Sets the panel header.
/// </summary>
/// <param name="panel">The panel.</param>
/// <param name="header">The header to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Panel SetHeader(this Panel panel, Header header)
{
if (panel is null)
{
throw new ArgumentNullException(nameof(panel));
}
panel.Header = header;
return panel;
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@@ -12,7 +12,7 @@ namespace Spectre.Console
/// of the paragraph can have individual styling. /// of the paragraph can have individual styling.
/// </summary> /// </summary>
[DebuggerDisplay("{_text,nq}")] [DebuggerDisplay("{_text,nq}")]
public sealed class Paragraph : Renderable, IAlignable public sealed class Paragraph : Renderable, IAlignable, IOverflowable
{ {
private readonly List<SegmentLine> _lines; private readonly List<SegmentLine> _lines;
@@ -21,6 +21,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
public Justify? Alignment { get; set; } public Justify? Alignment { get; set; }
/// <summary>
/// Gets or sets the text overflow strategy.
/// </summary>
public Overflow? Overflow { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Paragraph"/> class. /// Initializes a new instance of the <see cref="Paragraph"/> class.
/// </summary> /// </summary>
@@ -117,7 +122,7 @@ namespace Spectre.Console
var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding))); var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding)));
var max = _lines.Max(x => x.CellWidth(context.Encoding)); var max = _lines.Max(x => x.CellWidth(context.Encoding));
return new Measurement(min, max); return new Measurement(min, Math.Min(max, maxWidth));
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -197,34 +202,76 @@ namespace Spectre.Console
var line = new SegmentLine(); var line = new SegmentLine();
var newLine = true; var newLine = true;
using (var iterator = new SegmentLineIterator(_lines))
using var iterator = new SegmentLineIterator(_lines);
var queue = new Queue<Segment>();
while (true)
{ {
while (iterator.MoveNext()) var current = (Segment?)null;
if (queue.Count == 0)
{ {
var current = iterator.Current; if (!iterator.MoveNext())
if (current == null)
{ {
throw new InvalidOperationException("Iterator returned empty segment."); break;
} }
if (newLine && current.IsWhiteSpace && !current.IsLineBreak) current = iterator.Current;
{ }
newLine = false; else
continue; {
} current = queue.Dequeue();
}
if (current == null)
{
throw new InvalidOperationException("Iterator returned empty segment.");
}
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
{
newLine = false; newLine = false;
continue;
}
if (current.IsLineBreak) newLine = false;
if (current.IsLineBreak)
{
line.Add(current);
lines.Add(line);
line = new SegmentLine();
newLine = true;
continue;
}
var length = current.CellLength(context.Encoding);
if (length > maxWidth)
{
// The current segment is longer than the width of the console,
// so we will need to crop it up, into new segments.
var segments = Segment.SplitOverflow(current, Overflow, context.Encoding, maxWidth);
if (segments.Count > 0)
{ {
line.Add(current); if (line.CellWidth(context.Encoding) + segments[0].CellLength(context.Encoding) > maxWidth)
lines.Add(line); {
line = new SegmentLine(); lines.Add(line);
newLine = true; line = new SegmentLine();
continue; newLine = true;
}
var length = current.CellLength(context.Encoding); segments.ForEach(s => queue.Enqueue(s));
continue;
}
else
{
// Add the segment and push the rest of them to the queue.
line.Add(segments[0]);
segments.Skip(1).ForEach(s => queue.Enqueue(s));
continue;
}
}
}
else
{
if (line.CellWidth(context.Encoding) + length > maxWidth) if (line.CellWidth(context.Encoding) + length > maxWidth)
{ {
line.Add(Segment.Empty); line.Add(Segment.Empty);
@@ -232,16 +279,16 @@ namespace Spectre.Console
line = new SegmentLine(); line = new SegmentLine();
newLine = true; newLine = true;
} }
if (newLine && current.IsWhiteSpace)
{
continue;
}
newLine = false;
line.Add(current);
} }
if (newLine && current.IsWhiteSpace)
{
continue;
}
newLine = false;
line.Add(current);
} }
// Flush remaining. // Flush remaining.

View File

@@ -13,10 +13,25 @@ namespace Spectre.Console.Rendering
[DebuggerDisplay("{Text,nq}")] [DebuggerDisplay("{Text,nq}")]
public class Segment public class Segment
{ {
private readonly bool _mutable;
private string _text;
/// <summary> /// <summary>
/// Gets the segment text. /// Gets the segment text.
/// </summary> /// </summary>
public string Text { get; } public string Text
{
get => _text;
private set
{
if (!_mutable)
{
throw new NotSupportedException();
}
_text = value;
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether or not this is an expicit line break /// Gets a value indicating whether or not this is an expicit line break
@@ -39,12 +54,12 @@ namespace Spectre.Console.Rendering
/// <summary> /// <summary>
/// Gets a segment representing a line break. /// Gets a segment representing a line break.
/// </summary> /// </summary>
public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true); public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true, false);
/// <summary> /// <summary>
/// Gets an empty segment. /// Gets an empty segment.
/// </summary> /// </summary>
public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain); public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain, false, false);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Segment"/> class. /// Initializes a new instance of the <see cref="Segment"/> class.
@@ -65,14 +80,16 @@ namespace Spectre.Console.Rendering
{ {
} }
private Segment(string text, Style style, bool lineBreak) private Segment(string text, Style style, bool lineBreak, bool mutable = true)
{ {
if (text is null) if (text is null)
{ {
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
Text = text.NormalizeLineEndings(); _mutable = mutable;
_text = text.NormalizeLineEndings();
Style = style; Style = style;
IsLineBreak = lineBreak; IsLineBreak = lineBreak;
IsWhiteSpace = string.IsNullOrWhiteSpace(text); IsWhiteSpace = string.IsNullOrWhiteSpace(text);
@@ -226,6 +243,100 @@ namespace Spectre.Console.Rendering
return lines; return lines;
} }
internal static IEnumerable<Segment> Merge(IEnumerable<Segment> segments)
{
var result = new List<Segment>();
var previous = (Segment?)null;
foreach (var segment in segments)
{
if (previous == null)
{
previous = segment;
continue;
}
// Same style?
if (previous.Style.Equals(segment.Style))
{
// Modify the content of the previous segment
previous.Text += segment.Text;
}
else
{
// Push the current one to the results.
result.Add(previous);
previous = segment;
}
}
if (previous != null)
{
result.Add(previous);
}
return result;
}
/// <summary>
/// Splits an overflowing segment into several new segments.
/// </summary>
/// <param name="segment">The segment to split.</param>
/// <param name="overflow">The overflow strategy to use.</param>
/// <param name="encoding">The encodign to use.</param>
/// <param name="width">The maxiumum width.</param>
/// <returns>A list of segments that has been split.</returns>
public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, Encoding encoding, int width)
{
if (segment is null)
{
throw new ArgumentNullException(nameof(segment));
}
if (segment.CellLength(encoding) <= width)
{
return new List<Segment>(1) { segment };
}
// Default to folding
overflow ??= Overflow.Fold;
var result = new List<Segment>();
if (overflow == Overflow.Fold)
{
var totalLength = segment.Text.CellLength(encoding);
var lengthLeft = totalLength;
while (lengthLeft > 0)
{
var index = totalLength - lengthLeft;
var take = Math.Min(width, totalLength - index);
result.Add(new Segment(segment.Text.Substring(index, take), segment.Style));
lengthLeft -= take;
}
}
else if (overflow == Overflow.Crop)
{
result.Add(new Segment(segment.Text.Substring(0, width), segment.Style));
}
else if (overflow == Overflow.Ellipsis)
{
result.Add(new Segment(segment.Text.Substring(0, width - 1) + "…", segment.Style));
}
return result;
}
internal static Segment TruncateWithEllipsis(string text, Style style, Encoding encoding, int maxWidth)
{
return SplitOverflow(
new Segment(text, style),
Overflow.Ellipsis,
encoding,
maxWidth).First();
}
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells) internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
{ {
foreach (var cell in cells) foreach (var cell in cells)

View File

@@ -18,7 +18,7 @@ namespace Spectre.Console
// https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394 // https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394
private List<int> CalculateColumnWidths(RenderContext options, int maxWidth) private List<int> CalculateColumnWidths(RenderContext options, int maxWidth)
{ {
var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth)); var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth)).ToArray();
var widths = width_ranges.Select(range => range.Max).ToList(); var widths = width_ranges.Select(range => range.Max).ToList();
var tableWidth = widths.Sum(); var tableWidth = widths.Sum();
@@ -117,9 +117,17 @@ namespace Spectre.Console
private int GetExtraWidth(bool includePadding) private int GetExtraWidth(bool includePadding)
{ {
var separators = _columns.Count - 1; var hideBorder = BorderKind == BorderKind.None;
var separators = hideBorder ? 0 : _columns.Count - 1;
var edges = hideBorder ? 0 : EdgeCount;
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0; var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0;
return separators + EdgeCount + padding;
if (!PadRightCell)
{
padding -= _columns.Last().Padding.Right;
}
return separators + edges + padding;
} }
} }
} }

View File

@@ -26,10 +26,10 @@ namespace Spectre.Console
public int RowCount => _rows.Count; public int RowCount => _rows.Count;
/// <inheritdoc/> /// <inheritdoc/>
public BorderKind Border { get; set; } = BorderKind.Square; public BorderKind BorderKind { get; set; } = BorderKind.Square;
/// <inheritdoc/> /// <inheritdoc/>
public Color? BorderColor { get; set; } public Style? BorderStyle { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public bool SafeBorder { get; set; } = true; public bool SafeBorder { get; set; } = true;
@@ -125,17 +125,19 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(columns)); 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) if (columns.Length > _columns.Count)
{ {
throw new InvalidOperationException("The number of row columns are greater than the number of table columns."); throw new InvalidOperationException("The number of row columns are greater than the number of table columns.");
} }
_rows.Add(columns.ToList()); _rows.Add(columns.ToList());
// Need to add missing columns?
if (columns.Length < _columns.Count)
{
var diff = _columns.Count - columns.Length;
Enumerable.Range(0, diff).ForEach(_ => _rows.Last().Add(Text.Empty));
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -168,13 +170,13 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); var border = SpectreBorder.GetBorder(BorderKind, (context.LegacyConsole || !context.Unicode) && SafeBorder);
var borderStyle = new Style(BorderColor, null, null); var borderStyle = BorderStyle ?? Style.Plain;
var tableWidth = maxWidth; var tableWidth = maxWidth;
var showBorder = Border != BorderKind.None; var showBorder = BorderKind != BorderKind.None;
var hideBorder = Border == BorderKind.None; var hideBorder = BorderKind == BorderKind.None;
var hasRows = _rows.Count > 0; var hasRows = _rows.Count > 0;
if (Width != null) if (Width != null)

View File

@@ -10,7 +10,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
[DebuggerDisplay("{_text,nq}")] [DebuggerDisplay("{_text,nq}")]
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
public sealed class Text : Renderable, IAlignable public sealed class Text : Renderable, IAlignable, IOverflowable
{ {
private readonly Paragraph _paragraph; private readonly Paragraph _paragraph;
@@ -38,6 +38,15 @@ namespace Spectre.Console
set => _paragraph.Alignment = value; set => _paragraph.Alignment = value;
} }
/// <summary>
/// Gets or sets the text overflow strategy.
/// </summary>
public Overflow? Overflow
{
get => _paragraph.Overflow;
set => _paragraph.Overflow = value;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace Spectre.Console.Rendering namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Contains extension methods for <see cref="IHasBorder"/>. /// Contains extension methods for <see cref="IHasBorder"/>.
@@ -8,13 +8,61 @@ namespace Spectre.Console.Rendering
public static class BorderExtensions public static class BorderExtensions
{ {
/// <summary> /// <summary>
/// Sets the border. /// Do not display a border.
/// </summary> /// </summary>
/// <typeparam name="T">The object that has a border.</typeparam> /// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param> /// <param name="obj">The object to set the border for.</param>
/// <param name="border">The border to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorder<T>(this T obj, BorderKind border) public static T NoBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.None);
}
/// <summary>
/// Display a square border.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SquareBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.Square);
}
/// <summary>
/// Display an ASCII border.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T AsciiBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.Ascii);
}
/// <summary>
/// Display a rounded border.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T RoundedBorder<T>(this T obj)
where T : class, IHasBorder
{
return SetBorderKind(obj, BorderKind.Rounded);
}
/// <summary>
/// Sets the border kind.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param>
/// <param name="border">The border kind to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderKind<T>(this T obj, BorderKind border)
where T : class, IHasBorder where T : class, IHasBorder
{ {
if (obj is null) if (obj is null)
@@ -22,14 +70,14 @@ namespace Spectre.Console.Rendering
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
obj.Border = border; obj.BorderKind = border;
return obj; return obj;
} }
/// <summary> /// <summary>
/// Disables the safe border. /// Disables the safe border.
/// </summary> /// </summary>
/// <typeparam name="T">The object that has a border.</typeparam> /// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border for.</param> /// <param name="obj">The object to set the border for.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T NoSafeBorder<T>(this T obj) public static T NoSafeBorder<T>(this T obj)
@@ -44,12 +92,31 @@ namespace Spectre.Console.Rendering
return obj; return obj;
} }
/// <summary>
/// Sets the border style.
/// </summary>
/// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border color for.</param>
/// <param name="style">The border style to set.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderStyle<T>(this T obj, Style style)
where T : class, IHasBorder
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.BorderStyle = style;
return obj;
}
/// <summary> /// <summary>
/// Sets the border color. /// Sets the border color.
/// </summary> /// </summary>
/// <typeparam name="T">The object that has a border.</typeparam> /// <typeparam name="T">An object type with a border.</typeparam>
/// <param name="obj">The object to set the border color for.</param> /// <param name="obj">The object to set the border color for.</param>
/// <param name="color">The color to set.</param> /// <param name="color">The border color to set.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetBorderColor<T>(this T obj, Color color) public static T SetBorderColor<T>(this T obj, Color color)
where T : class, IHasBorder where T : class, IHasBorder
@@ -59,7 +126,7 @@ namespace Spectre.Console.Rendering
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
obj.BorderColor = color; obj.BorderStyle = (obj.BorderStyle ?? Style.Plain).WithForeground(color);
return obj; return obj;
} }
} }

View File

@@ -1,6 +1,6 @@
using System; using System;
namespace Spectre.Console.Rendering namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Contains extension methods for <see cref="IExpandable"/>. /// Contains extension methods for <see cref="IExpandable"/>.

View File

@@ -0,0 +1,80 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IOverflowable"/>.
/// </summary>
public static class OverflowableExtensions
{
/// <summary>
/// Folds any overflowing text.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IOverflowable"/>.</typeparam>
/// <param name="obj">The overflowable object instance.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Fold<T>(this T obj)
where T : class, IOverflowable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetOverflow(obj, Overflow.Fold);
}
/// <summary>
/// Crops any overflowing text.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IOverflowable"/>.</typeparam>
/// <param name="obj">The overflowable object instance.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Crop<T>(this T obj)
where T : class, IOverflowable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetOverflow(obj, Overflow.Crop);
}
/// <summary>
/// Crops any overflowing text and adds an ellipsis to the end.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IOverflowable"/>.</typeparam>
/// <param name="obj">The overflowable object instance.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Ellipsis<T>(this T obj)
where T : class, IOverflowable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetOverflow(obj, Overflow.Ellipsis);
}
/// <summary>
/// Sets the overflow strategy.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IOverflowable"/>.</typeparam>
/// <param name="obj">The overflowable object instance.</param>
/// <param name="overflow">The overflow strategy to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T SetOverflow<T>(this T obj, Overflow overflow)
where T : class, IOverflowable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Overflow = overflow;
return obj;
}
}
}

View File

@@ -15,11 +15,11 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the kind of border to use. /// Gets or sets the kind of border to use.
/// </summary> /// </summary>
public BorderKind Border { get; set; } public BorderKind BorderKind { get; set; }
/// <summary> /// <summary>
/// Gets or sets the border color. /// Gets or sets the border style.
/// </summary> /// </summary>
public Color? BorderColor { get; set; } public Style? BorderStyle { get; set; }
} }
} }

View File

@@ -0,0 +1,13 @@
namespace Spectre.Console
{
/// <summary>
/// Represents something that can overflow.
/// </summary>
public interface IOverflowable
{
/// <summary>
/// Gets or sets the text overflow strategy.
/// </summary>
Overflow? Overflow { get; set; }
}
}

View File

@@ -0,0 +1,40 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents color and text decoration.
/// </summary>
public sealed partial class Style : IEquatable<Style>
{
/// <summary>
/// Creates a new style from the specified foreground color.
/// </summary>
/// <param name="color">The foreground color.</param>
/// <returns>A new <see cref="Style"/> with the specified foreground color.</returns>
public static Style WithForeground(Color color)
{
return new Style(foreground: color);
}
/// <summary>
/// Creates a new style from the specified background color.
/// </summary>
/// <param name="color">The background color.</param>
/// <returns>A new <see cref="Style"/> with the specified background color.</returns>
public static Style WithBackground(Color color)
{
return new Style(background: color);
}
/// <summary>
/// Creates a new style from the specified text decoration.
/// </summary>
/// <param name="decoration">The text decoration.</param>
/// <returns>A new <see cref="Style"/> with the specified text decoration.</returns>
public static Style WithDecoration(Decoration decoration)
{
return new Style(decoration: decoration);
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents color and text decoration. /// Represents color and text decoration.
/// </summary> /// </summary>
public sealed class Style : IEquatable<Style> public sealed partial class Style : IEquatable<Style>
{ {
/// <summary> /// <summary>
/// Gets the foreground color. /// Gets the foreground color.

View File

@@ -0,0 +1,70 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Style"/>.
/// </summary>
public static class StyleExtensions
{
/// <summary>
/// Creates a new style from the specified one with
/// the specified foreground color.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="color">The foreground color.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithForeground(this Style style, Color color)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: color,
background: style.Background,
decoration: style.Decoration);
}
/// <summary>
/// Creates a new style from the specified one with
/// the specified background color.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="color">The background color.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithBackground(this Style style, Color color)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: style.Foreground,
background: color,
decoration: style.Decoration);
}
/// <summary>
/// Creates a new style from the specified one with
/// the specified text decoration.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="decoration">The text decoration.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithDecoration(this Style style, Decoration decoration)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: style.Foreground,
background: style.Background,
decoration: decoration);
}
}
}