Compare commits

..

24 Commits

Author SHA1 Message Date
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
Patrik Svensson
47fd646d21 Enable publish for all tags (skip-ci) 2020-08-30 10:22:53 +02:00
Patrik Svensson
a06859dcb5 Only publish on tags (skip-ci) 2020-08-30 10:12:53 +02:00
Patrik Svensson
7d3a67e6ae Always publish tags (skip-ci) 2020-08-30 10:04:29 +02:00
Patrik Svensson
f4497b1278 Docs: Added color number (skip-ci) 2020-08-29 11:25:19 +02:00
Patrik Svensson
a16daade6c Fallback to default buffer size
Also fixes a bug when using Spectre.Console in GitHub Actions.
2020-08-28 11:56:54 +02:00
Khalid Abuhakmeh
ab73d16583 Fix Unescaped Bracket
Was throwing error because of unescaped end bracket
at the end of options.
2020-08-27 20:53:10 +02:00
Patrik Svensson
f2566f2ca4 Remove commented out code (skip-ci) 2020-08-27 20:13:35 +02:00
Patrik Svensson
358b0225b4 Add documentation header (skip-ci) 2020-08-27 20:12:13 +02:00
46 changed files with 857 additions and 269 deletions

View File

@@ -6,6 +6,35 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: true
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:
name: Build
if: "!contains(github.event.head_commit.message, 'skip-ci')"
@@ -31,6 +60,16 @@ jobs:
with:
dotnet-version: 3.1.301
- name: Integration Tests
shell: bash
run: |
dotnet tool restore
dotnet example diagnostic
dotnet example table
dotnet example grid
dotnet example panel
dotnet example colors
- name: Build
shell: bash
run: |

View File

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

View File

@@ -6,6 +6,8 @@ on:
- '*'
branches:
- main
paths:
- 'src/**'
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
@@ -13,13 +15,38 @@ env:
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:
name: Build
if: "!contains(github.event.head_commit.message, 'skip-ci')"
needs: [docs]
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
strategy:
matrix:
kind: ['linux', 'windows', 'macOS']
@@ -55,7 +82,7 @@ jobs:
publish:
name: Publish
needs: [build]
if: "!contains(github.event.head_commit.message, 'skip-ci')"
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

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

1
docs/Preview.ps1 Normal file
View File

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

8
docs/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Documentation
Preview the documentation locally by running the following
from your favourite shell:
```
> dotnet run -- preview --virtual-dir "spectre.console"
```

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
@@ -26,13 +26,34 @@
</head>
<body class="d-flex flex-column">
<div class="flex-grow-1 d-flex flex-column">
<nav id="topnav" class="navbar navbar-expand-lg navbar-light">
<div class="container py-3">
<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">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav mr-auto">
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link font-weight-light font-size-lg text-red" href="https://github.com/sponsors/patriksvensson"><i class="far fa-heart"></i> Sponsor</a>
</li>
<li class="nav-item">
<a class="nav-link font-weight-light font-size-lg" href="https://github.com/spectresystems/spectre.console"><i class="fab fa-github"></i> GitHub</a>
</li>
</ul>
</div>
</div>
</nav>
@if (IsSectionDefined(Constants.Sections.Splash))
{
@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)">
@@ -41,7 +62,7 @@
<div id="titlebar" class="py-4">
<div class="container">
<div class="row">
@{
@{
string titleBarClasses = Document.GetBool(Constants.NoSidebar) ? string.Empty : "offset-md-3 offset-lg-2";
}
<div class="@titleBarClasses px-3 px-md-0">
@@ -76,7 +97,7 @@
</div>
</div>
}
<div class="flex-grow-1 d-flex flex-column bg-body">
@if (Document.GetBool(Constants.NoContainer))
{
@@ -105,21 +126,22 @@
}
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)">
@Html.DocumentLink(root)
</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)
</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)">
@foreach (IDocument child in document.GetChildren().OnlyVisible())
<div class="sidebar-nav-children @(Document.IdEquals(document) || documentChildren.Any(x => Document.IdEquals(x)) ? "active" : null)">
@foreach (IDocument child in documentChildren.OnlyVisible())
{
<div class="sidebar-nav-child @(Document.IdEquals(child) ? "active" : null)">
@Html.DocumentLink(child)
@@ -161,7 +183,10 @@
</div>
<div id="footer" class="p-3 text-white font-size-sm">
<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>
<script>
@@ -174,7 +199,7 @@
},
startOnLoad: false,
cloneCssStyles: false
});
});
mermaid.init(undefined, ".mermaid");
// Remove the max-width setting that Mermaid sets
@@ -193,13 +218,13 @@
center: true,
maxZoom: 20,
zoomScaleSensitivity: 0.6
});
});
// Do the reset once right away to fit the diagram
panZoom.resize();
panZoom.fit();
panZoom.center();
$(window).resize(function(){
panZoom.resize();
panZoom.fit();

View File

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

View File

@@ -18,6 +18,18 @@ $thebackground: $gray-200;
display: inline-block;
width: 60px;
height: 15px;
border: 2px solid #000000;
}
#topnav {
padding: 0px;
margin: 0px;
}
#topnav .container {
padding-top: 0px !important;
padding-bottom: 0px !important;
margin-bottom: 0px;
}
@media screen {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +1,5 @@
Title: Colors
Order: 2
Order: 4
---
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,84 +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>
}
@* <div class="container py-3">
<div class="row">
<div class="col-sm py-3">
<div class="row">
<div class="col-md-2">
<i class="fad fa-heartbeat fa-2x icon-md-100 icon-theme"></i>
</div>
<div class="col-md-10">
<h3 class="font-weight-bold font-size-base text-dark mb-2">A beautiful console</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Aenean elementum est lorem, at auctor nibh rhoncus eget.
Ut lorem leo, aliquet sit amet tortor vitae, aliquet eleifend dui.
Etiam lectus diam, maximus a pretium a, lacinia in augue.
</p>
</div>
</div>
</div>
<div class="col-sm py-3">
<div class="row">
<div class="col-md-2">
<i class="fad fa-code fa-2x icon-md-100 icon-theme"></i>
</div>
<div class="col-md-10">
<h3 class="font-weight-bold font-size-base text-dark mb-2">Rich formatting language</h3>
<p>
Donec at rutrum turpis. Etiam tempor sed ex quis pharetra.
Suspendisse ullamcorper at magna ac efficitur. Nulla ornare,
lectus feugiat ornare ultricies, nunc lorem maximus ipsum,
nec accumsan eros tellus quis ante
</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm py-3">
<div class="row">
<div class="col-md-2">
<i class="fad fa-cogs fa-2x icon-md-100 icon-theme"></i>
</div>
<div class="col-md-10">
<h3 class="font-weight-bold font-size-base text-dark mb-2">Configurable</h3>
<p>
Suspendisse vel condimentum tortor. Fusce tempus ligula ut eros finibus,
nec fermentum ipsum hendrerit. Proin eu diam in tortor vestibulum bibendum.
Integer tincidunt felis eu urna pulvinar vehicula.
</p>
</div>
</div>
</div>
<div class="col-sm py-3">
<div class="row">
<div class="col-md-2">
<i class="fad fa-search fa-2x icon-md-100 icon-theme"></i>
</div>
<div class="col-md-10">
<h3 class="font-weight-bold font-size-base text-dark mb-2">Find problems quickly</h3>
<p>
Aliquam nec nisl ullamcorper, aliquam mauris sit amet, sagittis orci.
Aenean sapien dui, eleifend eget sapien nec, tristique accumsan tortor.
Quisque ex quam, rhoncus sagittis consequat id, euismod sit amet dolor.
</p>
</div>
</div>
</div>
</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)
inspired markup language.
* 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,
and blinking text.
* 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;
}
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)
{
return !document.GetBool(Constants.Hidden, false);

View File

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

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NJsonSchema;
using Statiq.Common;
using System.Xml.Linq;
using Docs.Pipelines;
@@ -26,6 +25,7 @@ namespace Docs.Shortcodes
var table = new XElement("table", new XAttribute("class", "table"));
var header = new XElement("tr", new XAttribute("class", "color-row"));
header.Add(new XElement("th", ""));
header.Add(new XElement("th", "#"));
header.Add(new XElement("th", "Name"));
header.Add(new XElement("th", "RGB"));
header.Add(new XElement("th", "Hex"));
@@ -38,7 +38,8 @@ namespace Docs.Shortcodes
new XElement("span",
new XAttribute("class", "color-representation"),
new XAttribute("style", $"background-color:{color.Hex};")));
var name = new XElement("td", new XElement("code", color.Name.ToLower()));
var name = new XElement("td", new XElement("code", color.Number.ToString()));
var number = new XElement("td", new XElement("code", color.Name.ToLower()));
var rgb = new XElement("td", new XElement("code", $"{color.R},{color.G},{color.B}"));
var hex = new XElement("td", new XElement("code", color.Hex));
var clr = new XElement("td", new XElement("code", color.ClrName));
@@ -47,6 +48,7 @@ namespace Docs.Shortcodes
var row = new XElement("tr");
row.Add(rep);
row.Add(name);
row.Add(number);
row.Add(rgb);
row.Add(hex);
row.Add(clr);

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

@@ -13,6 +13,12 @@
"commands": [
"gpr"
]
},
"dotnet-example": {
"version": "0.8.0",
"commands": [
"dotnet-example"
]
}
}
}

View File

@@ -2,75 +2,117 @@ using Spectre.Console;
namespace ColorExample
{
class Program
public static class Program
{
static void Main(string[] args)
public static void Main(string[] args)
{
/////////////////////////////////////////////////////////////////
// 4-BIT
/////////////////////////////////////////////////////////////////
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]4-bit Colors[/]");
AnsiConsole.WriteLine();
for (var i = 0; i < 16; i++)
if (AnsiConsole.Capabilities.ColorSystem == ColorSystem.NoColors)
{
AnsiConsole.Background = Color.FromInt32(i);
AnsiConsole.Write(string.Format(" {0,-9}", AnsiConsole.Background.ToString()));
AnsiConsole.ResetColors();
if ((i + 1) % 8 == 0)
{
AnsiConsole.WriteLine();
}
/////////////////////////////////////////////////////////////////
// No colors
/////////////////////////////////////////////////////////////////
AnsiConsole.WriteLine("No colors are supported.");
return;
}
/////////////////////////////////////////////////////////////////
// 8-BIT
/////////////////////////////////////////////////////////////////
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]8-bit Colors[/]");
AnsiConsole.WriteLine();
for (var i = 0; i < 16; i++)
if (AnsiConsole.Capabilities.Supports(ColorSystem.Legacy))
{
for (var j = 0; j < 16; j++)
/////////////////////////////////////////////////////////////////
// 3-BIT
/////////////////////////////////////////////////////////////////
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]3-bit Colors[/]");
AnsiConsole.WriteLine();
for (var i = 0; i < 8; i++)
{
var number = i * 16 + j;
AnsiConsole.Background = Color.FromInt32(number);
AnsiConsole.Write(string.Format(" {0,-4}", number));
AnsiConsole.Background = Color.FromInt32(i);
AnsiConsole.Write(string.Format(" {0,-9}", AnsiConsole.Background.ToString()));
AnsiConsole.ResetColors();
if ((number + 1) % 16 == 0)
if ((i + 1) % 8 == 0)
{
AnsiConsole.WriteLine();
}
}
}
/////////////////////////////////////////////////////////////////
// 24-BIT
/////////////////////////////////////////////////////////////////
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]24-bit Colors[/]");
AnsiConsole.WriteLine();
var index = 0;
for (var i = 0.0005; i < 1; i += 0.0025)
if (AnsiConsole.Capabilities.Supports(ColorSystem.Standard))
{
index++;
/////////////////////////////////////////////////////////////////
// 4-BIT
/////////////////////////////////////////////////////////////////
var color = Utilities.HSL2RGB(i, 0.5, 0.5);
AnsiConsole.Background = new Color(color.R, color.G, color.B);
AnsiConsole.Write(" ");
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]4-bit Colors[/]");
AnsiConsole.WriteLine();
if (index % 50 == 0)
for (var i = 0; i < 16; i++)
{
AnsiConsole.WriteLine();
AnsiConsole.Background = Color.FromInt32(i);
AnsiConsole.Write(string.Format(" {0,-9}", AnsiConsole.Background.ToString()));
AnsiConsole.ResetColors();
if ((i + 1) % 8 == 0)
{
AnsiConsole.WriteLine();
}
}
}
if (AnsiConsole.Capabilities.Supports(ColorSystem.EightBit))
{
/////////////////////////////////////////////////////////////////
// 8-BIT
/////////////////////////////////////////////////////////////////
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]8-bit Colors[/]");
AnsiConsole.WriteLine();
for (var i = 0; i < 16; i++)
{
for (var j = 0; j < 16; j++)
{
var number = i * 16 + j;
AnsiConsole.Background = Color.FromInt32(number);
AnsiConsole.Write(string.Format(" {0,-4}", number));
AnsiConsole.ResetColors();
if ((number + 1) % 16 == 0)
{
AnsiConsole.WriteLine();
}
}
}
}
if (AnsiConsole.Capabilities.Supports(ColorSystem.TrueColor))
{
/////////////////////////////////////////////////////////////////
// 24-BIT
/////////////////////////////////////////////////////////////////
AnsiConsole.ResetColors();
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[bold underline]24-bit Colors[/]");
AnsiConsole.WriteLine();
var index = 0;
for (var i = 0.0005; i < 1; i += 0.0025)
{
index++;
var color = Utilities.HSL2RGB(i, 0.5, 0.5);
AnsiConsole.Background = new Color(color.R, color.G, color.B);
AnsiConsole.Write(" ");
if (index % 50 == 0)
{
AnsiConsole.WriteLine();
}
}
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Description>Displays the capabilities of the current console.</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using System;
using Spectre.Console;
namespace Diagnostic
{
public class Program
{
public static void Main(string[] args)
{
AnsiConsole.MarkupLine("Color system: [bold]{0}[/]", AnsiConsole.Capabilities.ColorSystem);
AnsiConsole.MarkupLine("Supports ansi? [bold]{0}[/]", AnsiConsole.Capabilities.SupportsAnsi);
AnsiConsole.MarkupLine("Legacy console? [bold]{0}[/]", AnsiConsole.Capabilities.LegacyConsole);
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("Buffer width: [bold]{0}[/]", AnsiConsole.Console.Width);
AnsiConsole.MarkupLine("Buffer height: [bold]{0}[/]", AnsiConsole.Console.Height);
}
}
}

View File

@@ -1,25 +1,25 @@
using Spectre.Console;
namespace GridExample
{
public sealed class Program
{
static void Main(string[] args)
{
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]");
AnsiConsole.WriteLine();
var grid = new Grid();
grid.AddColumn(new GridColumn { NoWrap = true });
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 });
grid.AddColumn();
grid.AddRow("Options:", "", "");
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help.");
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for.");
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the [grey]MSBuild[/] verbosity level.");
AnsiConsole.Render(grid);
}
}
}
using Spectre.Console;
namespace GridExample
{
public sealed class Program
{
static void Main(string[] args)
{
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options]] [[[[--]] <additional arguments>...]]]][/]");
AnsiConsole.WriteLine();
var grid = new Grid();
grid.AddColumn(new GridColumn { NoWrap = true });
grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 });
grid.AddColumn();
grid.AddRow("Options:", "", "");
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "", "Show command line help.");
grid.AddRow(" [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for.");
grid.AddRow(" [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the [grey]MSBuild[/] verbosity level.");
AnsiConsole.Render(grid);
}
}
}

View File

@@ -7,9 +7,8 @@ namespace PanelExample
static void Main(string[] args)
{
var content = new Markup(
"[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" +
"So I put a 📦 in a 📦\n\n" +
"😅").Centered();
"[underline]I[/] heard [underline on blue]you[/] like panels\n\n\n\n" +
"So I put a panel in a panel").Centered();
AnsiConsole.Render(
new Panel(

View File

@@ -41,16 +41,16 @@ namespace TableExample
table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true });
// Add some rows
table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍");
table.AddRow("[blue][underline]Hell[/]o[/]", "World");
table.AddRow("[yellow]Patrik [green]\"Hello World\"[/] Svensson[/]", "Was [underline]here[/]!");
table.AddEmptyRow();
table.AddRow(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " +
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " +
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", " Strange language");
"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "<- Strange language");
table.AddEmptyRow();
table.AddRow("Hej 👋", "[green]Världen[/]");
table.AddRow("Hej", "[green]Världen[/]");
AnsiConsole.Render(table);
}
@@ -81,7 +81,7 @@ namespace TableExample
table.AddColumn(new TableColumn(new Panel("[u]Baz[/]").SetBorderColor(Color.Blue)));
// Add some rows
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty);
table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/]"), Text.Empty);
table.AddRow(second, new Text("Whaaat"), new Text("Lol"));
table.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty);

View File

@@ -12,7 +12,7 @@ namespace Spectre.Console.Tests.Unit
{
[Theory]
[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)
{
// Given
@@ -26,7 +26,7 @@ namespace Spectre.Console.Tests.Unit
}
[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)
{
// Given

View File

@@ -83,5 +83,25 @@ namespace Spectre.Console.Tests.Unit
.NormalizeLineEndings()
.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

@@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grid", "..\examples\Grid\Gr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Colors", "..\examples\Colors\Colors.csproj", "{1F51C55C-BA4C-4856-9001-0F7924FFB179}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagnostic", "..\examples\Diagnostic\Diagnostic.csproj", "{4337F255-88E9-4408-81A3-DF1AF58AC753}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -107,6 +109,18 @@ Global
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x64.Build.0 = Release|Any CPU
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x86.ActiveCfg = Release|Any CPU
{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x86.Build.0 = Release|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Debug|x64.ActiveCfg = Debug|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Debug|x64.Build.0 = Debug|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Debug|x86.ActiveCfg = Debug|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Debug|x86.Build.0 = Debug|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|Any CPU.Build.0 = Release|Any CPU
{4337F255-88E9-4408-81A3-DF1AF58AC753}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -116,6 +130,7 @@ Global
{BFF37228-B376-4ADD-9657-4E501F929713} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{C7FF6FDB-FB59-4517-8669-521C96AB7323} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{1F51C55C-BA4C-4856-9001-0F7924FFB179} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{4337F255-88E9-4408-81A3-DF1AF58AC753} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}

View File

@@ -38,6 +38,17 @@ namespace Spectre.Console
LegacyConsole = legacyConsole;
}
/// <summary>
/// Checks whether the current capabilities supports
/// the specified color system.
/// </summary>
/// <param name="colorSystem">The color system to check.</param>
/// <returns><c>true</c> if the color system is supported, otherwise <c>false</c>.</returns>
public bool Supports(ColorSystem colorSystem)
{
return (int)colorSystem <= (int)ColorSystem;
}
/// <inheritdoc/>
public override string ToString()
{

View File

@@ -30,8 +30,11 @@ namespace Spectre.Console
using (console.PushStyle(Style.Plain))
{
var segments = renderable.Render(options, console.Width);
segments = Segment.Merge(segments);
var current = Style.Plain;
foreach (var segment in renderable.Render(options, console.Width))
foreach (var segment in segments)
{
if (string.IsNullOrEmpty(segment.Text))
{

View File

@@ -21,7 +21,7 @@ namespace Spectre.Console.Internal
{
if (_out.IsStandardOut())
{
return System.Console.BufferWidth;
return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth);
}
return Constants.DefaultBufferWidth;
@@ -34,7 +34,7 @@ namespace Spectre.Console.Internal
{
if (_out.IsStandardOut())
{
return System.Console.BufferHeight;
return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight);
}
return Constants.DefaultBufferHeight;

View File

@@ -3,7 +3,7 @@ using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class ConsoleExtensions
internal static class AnsiConsoleExtensions
{
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
{

View File

@@ -24,7 +24,7 @@ namespace Spectre.Console.Internal
{
if (_out.IsStandardOut())
{
return System.Console.BufferWidth;
return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth);
}
return Constants.DefaultBufferWidth;
@@ -37,7 +37,7 @@ namespace Spectre.Console.Internal
{
if (_out.IsStandardOut())
{
return System.Console.BufferHeight;
return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight);
}
return Constants.DefaultBufferHeight;

View File

@@ -0,0 +1,43 @@
using System.IO;
namespace Spectre.Console.Internal
{
internal static class ConsoleHelper
{
public static int GetSafeBufferWidth(int defaultValue = Constants.DefaultBufferWidth)
{
try
{
var width = System.Console.BufferWidth;
if (width == 0)
{
width = defaultValue;
}
return width;
}
catch (IOException)
{
return defaultValue;
}
}
public static int GetSafeBufferHeight(int defaultValue = Constants.DefaultBufferWidth)
{
try
{
var height = System.Console.BufferHeight;
if (height == 0)
{
height = defaultValue;
}
return height;
}
catch (IOException)
{
return defaultValue;
}
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Spectre.Console
/// <summary>
/// A renderable piece of markup text.
/// </summary>
public sealed class Markup : Renderable, IAlignable
public sealed class Markup : Renderable, IAlignable, IOverflowable
{
private readonly Paragraph _paragraph;
@@ -18,6 +18,13 @@ namespace Spectre.Console
set => _paragraph.Alignment = value;
}
/// <inheritdoc/>
public Overflow? Overflow
{
get => _paragraph.Overflow;
set => _paragraph.Overflow = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Markup"/> class.
/// </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,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -12,7 +12,7 @@ namespace Spectre.Console
/// of the paragraph can have individual styling.
/// </summary>
[DebuggerDisplay("{_text,nq}")]
public sealed class Paragraph : Renderable, IAlignable
public sealed class Paragraph : Renderable, IAlignable, IOverflowable
{
private readonly List<SegmentLine> _lines;
@@ -21,6 +21,11 @@ namespace Spectre.Console
/// </summary>
public Justify? Alignment { get; set; }
/// <summary>
/// Gets or sets the text overflow strategy.
/// </summary>
public Overflow? Overflow { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Paragraph"/> class.
/// </summary>
@@ -197,34 +202,76 @@ namespace Spectre.Console
var line = new SegmentLine();
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 (current == null)
if (!iterator.MoveNext())
{
throw new InvalidOperationException("Iterator returned empty segment.");
break;
}
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
{
newLine = false;
continue;
}
current = iterator.Current;
}
else
{
current = queue.Dequeue();
}
if (current == null)
{
throw new InvalidOperationException("Iterator returned empty segment.");
}
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
{
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);
lines.Add(line);
line = new SegmentLine();
newLine = true;
continue;
}
if (line.CellWidth(context.Encoding) + segments[0].CellLength(context.Encoding) > maxWidth)
{
lines.Add(line);
line = new SegmentLine();
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)
{
line.Add(Segment.Empty);
@@ -232,16 +279,16 @@ namespace Spectre.Console
line = new SegmentLine();
newLine = true;
}
if (newLine && current.IsWhiteSpace)
{
continue;
}
newLine = false;
line.Add(current);
}
if (newLine && current.IsWhiteSpace)
{
continue;
}
newLine = false;
line.Add(current);
}
// Flush remaining.

View File

@@ -16,7 +16,7 @@ namespace Spectre.Console.Rendering
/// <summary>
/// Gets the segment text.
/// </summary>
public string Text { get; }
public string Text { get; internal set; }
/// <summary>
/// Gets a value indicating whether or not this is an expicit line break
@@ -226,6 +226,92 @@ namespace Spectre.Console.Rendering
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));
result.Add(new Segment("…", segment.Style));
}
return result;
}
internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells)
{
foreach (var cell in cells)

View File

@@ -10,7 +10,7 @@ namespace Spectre.Console
/// </summary>
[DebuggerDisplay("{_text,nq}")]
[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;
@@ -38,6 +38,15 @@ namespace Spectre.Console
set => _paragraph.Alignment = value;
}
/// <summary>
/// Gets or sets the text overflow strategy.
/// </summary>
public Overflow? Overflow
{
get => _paragraph.Overflow;
set => _paragraph.Overflow = value;
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{

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

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