Compare commits

...

14 Commits

Author SHA1 Message Date
Patrik Svensson
3847a8949f Fix bug with uris being interpreted as emojis
Closes #82
2020-09-20 13:00:44 +02:00
Patrik Svensson
eeb3f967b6 Update emoji support
* Add constants for emojis
* Move emoji shortcode rendering to Markup
* Add documentation
* Add example
* Add tests
2020-09-18 16:11:51 +02:00
Patrik Svensson
090b30f731 Use Wcwidth library 2020-09-18 15:31:12 +02:00
Patrik Svensson
df291ef84e Fix Info example emoji problem
The emojis that previously were used, used Unicode combinators
which are not fully supported. Changing to :thumbs_up: and :thumbs_down:
instead.
2020-09-17 10:58:50 +02:00
Patrik Svensson
7d6104ace4 Add padder widget
This commit adds a padder can be use to pad other IRenderable
objects such as tables, panels, grids, text, etc.
2020-09-17 10:58:50 +02:00
Kristian Hellang
314456ca17 Add emoji codes to example 2020-09-17 10:35:15 +02:00
Kristian Hellang
b7f654cd7f Replace emoji in segment text 2020-09-17 10:35:15 +02:00
Kristian Hellang
fea8a36e8a Add generated Emoji class with corresponding non-generated file 2020-09-17 10:35:15 +02:00
Kristian Hellang
0632b38477 Fix relative path in existing color script 2020-09-17 10:35:15 +02:00
Kristian Hellang
a7b7d4e556 Add Generator command to generate emoji lookup table 2020-09-17 10:35:15 +02:00
Kristian Hellang
11d331e31d Add .DS_Store to .gitignore 2020-09-17 10:35:15 +02:00
Patrik Svensson
ce670a7ca9 Add link identity generator 2020-09-12 14:47:32 +02:00
Patrik Svensson
101e244059 Minor clean up 2020-09-12 10:46:57 +02:00
Patrik Svensson
504746c5dc Add link support for supported terminals
Also refactors the code quite a bit, to make it a bit more
easier to add features like this in the future.

Closes #75
2020-09-11 17:44:56 +02:00
97 changed files with 19124 additions and 1935 deletions

View File

@@ -69,6 +69,7 @@ jobs:
dotnet example grid dotnet example grid
dotnet example panel dotnet example panel
dotnet example colors dotnet example colors
dotnet example emojis
- name: Build - name: Build
shell: bash shell: bash

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
[Pp]ackages/ [Pp]ackages/
/.artifacts/ /.artifacts/
/[Tt]ools/ /[Tt]ools/
.DS_Store
# Cakeup # Cakeup
cakeup-x86_64-latest.exe cakeup-x86_64-latest.exe

View File

@@ -22,11 +22,23 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="src\Data\emojis.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="src\Data\emojis.json" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Statiq.Web" Version="1.0.0-beta.5" /> <PackageReference Include="Statiq.Web" Version="1.0.0-beta.5" />
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" /> <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="input\assets\images\emojis\" />
</ItemGroup>
<Target Name="Versioning" BeforeTargets="MinVer"> <Target Name="Versioning" BeforeTargets="MinVer">
<PropertyGroup Label="Build"> <PropertyGroup Label="Build">
<MinVerDefaultPreReleasePhase>preview</MinVerDefaultPreReleasePhase> <MinVerDefaultPreReleasePhase>preview</MinVerDefaultPreReleasePhase>

View File

@@ -19,6 +19,7 @@ namespace Docs
.ConfigureDeployment(deployBranch: "docs") .ConfigureDeployment(deployBranch: "docs")
.AddShortcode("Children", typeof(ChildrenShortcode)) .AddShortcode("Children", typeof(ChildrenShortcode))
.AddShortcode("ColorTable", typeof(ColorTableShortcode)) .AddShortcode("ColorTable", typeof(ColorTableShortcode))
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
.AddPipelines() .AddPipelines()
.RunAsync(); .RunAsync();

View File

@@ -128,14 +128,28 @@
{ {
IDocument root = OutputPages["index.html"].First(); 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) @if(root.ShowLink())
{
@Html.DocumentLink(root)
}
else
{
@root.GetTitle()
}
</div> </div>
@foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible()) @foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible())
{ {
DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document); DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document);
<div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)"> <div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)">
@Html.DocumentLink(document) @if(document.ShowLink())
{
@Html.DocumentLink(document)
}
else
{
@document.GetTitle()
}
</div> </div>
@if (documentChildren.OnlyVisible().Any()) @if (documentChildren.OnlyVisible().Any())

View File

@@ -0,0 +1,38 @@
Title: Emojis
Order: 3
---
Please note that what emojis that can be used is completely up to
the operating system and/or terminal you're using, and no guarantees
can be made of how it will look. Calculating the width of emojis
is also not an exact science in many ways, so milage might vary when
used in tables, panels or grids.
To ensure best compatibility, consider only using emojis introduced
before Unicode 13.0 that belongs in the `Emoji_Presentation` category
in the official emoji list at
https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-data.txt
# Usage
```csharp
// Markup
AnsiConsole.MarkupLine("Hello :globe_showing_europe_africa:!");
// Constant
var hello = "Hello " + Emoji.Known.GlobeShowingEuropeAfrica;
```
# Replacing emojis in text
```csharp
var phrase = "Mmmm :birthday_cake:";
var rendered = Emoji.Replace(phrase);
```
# Emojis
_The images in the table below might not render correctly in your
browser for the same reasons mentioned in the `Compatibility` section._
<?# EmojiTable /?>

View File

@@ -1,3 +1,10 @@
Title: Appendix Title: Appendix
Order: 10 Order: 10
--- ---
# Sections
* [Styles](xref:styles)
* [Colors](xref:colors)
* [Borders](xref:borders)
* [Emojis](xref:emojis)

View File

@@ -2,8 +2,7 @@ Title: Markup
Order: 2 Order: 2
--- ---
In `Spectre.Console` there's a class called `Markup` that The class `Markup` allows you to output rich text to the console.
allows you to output rich text to the console.
# Syntax # Syntax
@@ -54,6 +53,16 @@ You can set the background color in markup by prefixing the color with
[default on blue]World[/] [default on blue]World[/]
``` ```
# Rendering emojis
To output an emoji as part of markup, you can use emoji shortcodes.
```csharp
AnsiConsole.MarkupLine("Hello :globe_showing_europe_africa:!");
```
For a list of emoji, see the [Emojis](xref:styles) appendix section.
# Colors # Colors
For a list of colors, see the [Colors](xref:colors) appendix section. For a list of colors, see the [Colors](xref:colors) appendix section.

View File

@@ -1,14 +1,20 @@
namespace Docs namespace Docs
{ {
public static class Constants public static class Constants
{ {
public const string NoContainer = nameof(NoContainer); public const string NoContainer = nameof(NoContainer);
public const string NoSidebar = nameof(NoSidebar); public const string NoSidebar = nameof(NoSidebar);
public const string NoLink = nameof(NoLink);
public const string Topic = nameof(Topic); public const string Topic = nameof(Topic);
public const string EditLink = nameof(EditLink); public const string EditLink = nameof(EditLink);
public const string Description = nameof(Description); public const string Description = nameof(Description);
public const string Hidden = nameof(Hidden); public const string Hidden = nameof(Hidden);
public static class Emojis
{
public const string Root = "EMOJIS_ROOT";
}
public static class Colors public static class Colors
{ {
public const string Url = "https://raw.githubusercontent.com/spectresystems/spectre.console/main/resources/scripts/Generator/Data/colors.json"; public const string Url = "https://raw.githubusercontent.com/spectresystems/spectre.console/main/resources/scripts/Generator/Data/colors.json";

7946
docs/src/Data/emojis.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
using Statiq.Common; using Statiq.Common;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -16,6 +16,11 @@ namespace Docs
return !document.GetBool(Constants.Hidden, false); return !document.GetBool(Constants.Hidden, false);
} }
public static bool ShowLink(this IDocument document)
{
return !document.GetBool(Constants.NoLink, false);
}
public static IEnumerable<IDocument> OnlyVisible(this IEnumerable<IDocument> source) public static IEnumerable<IDocument> OnlyVisible(this IEnumerable<IDocument> source)
{ {
return source.Where(x => x.IsVisible()); return source.Where(x => x.IsVisible());

20
docs/src/Models/Emoji.cs Normal file
View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
namespace Docs.Models
{
public sealed class Emoji
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Code { get; set; }
public static List<Emoji> Parse(string json)
{
return JsonConvert.DeserializeObject<List<Emoji>>(json);
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Statiq.Common;
namespace Docs.Modules
{
public sealed class ReadEmbedded : Module
{
private readonly System.Reflection.Assembly _assembly;
private readonly string _resource;
public ReadEmbedded(System.Reflection.Assembly assembly, string resource)
{
_assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
_resource = resource ?? throw new ArgumentNullException(nameof(resource));
}
protected override Task<IEnumerable<IDocument>> ExecuteContextAsync(IExecutionContext context)
{
return Task.FromResult((IEnumerable<IDocument>)new[]
{
context.CreateDocument(ReadResource()),
});
}
private Stream ReadResource()
{
var resourceName = _resource.Replace("/", ".");
var stream = _assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
throw new InvalidOperationException("Could not load manifest resource stream.");
}
return stream;
}
}
}

View File

@@ -26,13 +26,8 @@ namespace Docs.Pipelines
new ExecuteConfig( new ExecuteConfig(
Config.FromDocument(async (doc, ctx) => Config.FromDocument(async (doc, ctx) =>
{ {
var colors = Color.Parse(await doc.GetContentStringAsync()).ToList(); var data = Color.Parse(await doc.GetContentStringAsync()).ToList();
var definitions = new List<IDocument> { colors.ToDocument(Constants.Colors.Root) }; return data.ToDocument(Constants.Colors.Root);
return doc.Clone(new MetadataDictionary
{
[Constants.Colors.Root] = definitions
});
})) }))
}; };
} }

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using Docs.Models;
using Docs.Modules;
using Statiq.Common;
using Statiq.Core;
namespace Docs.Pipelines
{
public class EmojiPipeline : Pipeline
{
public EmojiPipeline()
{
InputModules = new ModuleList
{
new ExecuteConfig(
Config.FromContext(ctx => {
return new ReadEmbedded(
typeof(EmojiPipeline).Assembly,
"Docs/src/Data/emojis.json");
}))
};
ProcessModules = new ModuleList
{
new ExecuteConfig(
Config.FromDocument(async (doc, ctx) =>
{
var data = Emoji.Parse(await doc.GetContentStringAsync());
return data.ToDocument(Constants.Emojis.Root);
}))
};
}
}
}

View File

@@ -17,11 +17,10 @@ namespace Docs.Shortcodes
// Get the definition. // Get the definition.
var colors = context.Outputs var colors = context.Outputs
.FromPipeline(nameof(ColorsPipeline)) .FromPipeline(nameof(ColorsPipeline))
.First()
.GetChildren(Constants.Colors.Root)
.OfType<ObjectDocument<List<Color>>>() .OfType<ObjectDocument<List<Color>>>()
.First().Object; .First().Object;
// Headers
var table = new XElement("table", new XAttribute("class", "table")); var table = new XElement("table", new XAttribute("class", "table"));
var header = new XElement("tr", new XAttribute("class", "color-row")); var header = new XElement("tr", new XAttribute("class", "color-row"));
header.Add(new XElement("th", "")); header.Add(new XElement("th", ""));

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Statiq.Common;
using System.Xml.Linq;
using Docs.Pipelines;
using Docs.Models;
namespace Docs.Shortcodes
{
public class EmojiTableShortcode : SyncShortcode
{
public override ShortcodeResult Execute(KeyValuePair<string, string>[] args, string content, IDocument document, IExecutionContext context)
{
var emojis = context.Outputs
.FromPipeline(nameof(EmojiPipeline))
.OfType<ObjectDocument<List<Emoji>>>()
.First().Object;
// Headers
var table = new XElement("table", new XAttribute("class", "table"));
var header = new XElement("tr", new XAttribute("class", "emoji-row"));
header.Add(new XElement("th", ""));
header.Add(new XElement("th", "Markup"));
header.Add(new XElement("th", "Constant"));
table.Add(header);
foreach (var emoji in emojis)
{
var code = emoji.Code.Replace("U+0000", "U+").Replace("U+000", "U+");
var icon = string.Format("&#x{0};", emoji.Code.Replace("U+", string.Empty));
var row = new XElement("tr");
row.Add(new XElement("td", icon));
row.Add(new XElement("td", new XElement("code", $":{emoji.Id}:")));
row.Add(new XElement("td", new XElement("code", emoji.Name)));
table.Add(row);
}
return table.ToString()
.Replace("&amp;#x", "&#x");
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Description>Demonstrates how to render emojis.</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,17 @@
using System;
using Spectre.Console;
namespace Emojis
{
public static class Program
{
public static void Main(string[] args)
{
// Markup
AnsiConsole.Render(
new Panel("[yellow]Hello :globe_showing_europe_africa:![/]")
.RoundedBorder()
.SetHeader("Markup"));
}
}
}

View File

@@ -9,15 +9,19 @@ namespace Info
var grid = new Grid() var grid = new Grid()
.AddColumn(new GridColumn().NoWrap().PadRight(4)) .AddColumn(new GridColumn().NoWrap().PadRight(4))
.AddColumn() .AddColumn()
.AddRow("[b]Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}") .AddRow("[b]:artist_palette: Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}")
.AddRow("[b]Supports ansi?[/]", $"{AnsiConsole.Capabilities.SupportsAnsi}") .AddRow("[b]:nail_polish: Supports ansi?[/]", $"{GetEmoji(AnsiConsole.Capabilities.SupportsAnsi)}")
.AddRow("[b]Legacy console?[/]", $"{AnsiConsole.Capabilities.LegacyConsole}") .AddRow("[b]:top_hat: Legacy console?[/]", $"{GetEmoji(AnsiConsole.Capabilities.LegacyConsole)}")
.AddRow("[b]Buffer width[/]", $"{AnsiConsole.Console.Width}") .AddRow("[b]:left_right_arrow: Buffer width[/]", $"{AnsiConsole.Console.Width}")
.AddRow("[b]Buffer height[/]", $"{AnsiConsole.Console.Height}"); .AddRow("[b]:up_down_arrow: Buffer height[/]", $"{AnsiConsole.Console.Height}");
AnsiConsole.Render( AnsiConsole.Render(
new Panel(grid) new Panel(grid)
.SetHeader("Information")); .SetHeader("Information"));
} }
private static string GetEmoji(bool value) => value
? ":thumbs_up:"
: ":thumbs_down:";
} }
} }

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Description>Demonstrates how to render links in a console.</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

21
examples/Links/Program.cs Normal file
View File

@@ -0,0 +1,21 @@
using Spectre.Console;
namespace Links
{
public static class Program
{
public static void Main()
{
if (AnsiConsole.Capabilities.SupportLinks)
{
AnsiConsole.MarkupLine("[link=https://patriksvensson.se]Click to visit my blog[/]!");
}
else
{
AnsiConsole.MarkupLine("[red]It looks like your terminal doesn't support links[/]");
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine("[yellow](╯°□°)╯[/]︵ [blue]┻━┻[/]");
}
}
}
}

View File

@@ -3,7 +3,7 @@
########################################################## ##########################################################
$Output = Join-Path $PSScriptRoot "Temp" $Output = Join-Path $PSScriptRoot "Temp"
$Source = Join-Path $PSScriptRoot "/../src/Spectre.Console" $Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console"
if(!(Test-Path $Output -PathType Container)) { if(!(Test-Path $Output -PathType Container)) {
New-Item -ItemType Directory -Path $Output | Out-Null New-Item -ItemType Directory -Path $Output | Out-Null

View File

@@ -0,0 +1,24 @@
##########################################################
# Script that generates the emoji lookup table.
##########################################################
$Output = Join-Path $PSScriptRoot "Temp"
$Source = Join-Path $PSScriptRoot "/../../src/Spectre.Console"
$Docs = Join-Path $PSScriptRoot "/../../docs/src/Data"
if(!(Test-Path $Output -PathType Container)) {
New-Item -ItemType Directory -Path $Output | Out-Null
}
# Generate the files
Push-Location Generator
&dotnet run -- emoji "$Output" --input $Output
if(!$?) {
Pop-Location
Throw "An error occured when generating code."
}
Pop-Location
# Copy the files to the correct location
Copy-Item (Join-Path "$Output" "Emoji.Generated.cs") -Destination "$Source/Emoji.Generated.cs"
Copy-Item (Join-Path "$Output" "emojis.json") -Destination "$Docs/emojis.json"

View File

@@ -55,5 +55,8 @@ namespace Generator.Commands
{ {
[CommandArgument(0, "<OUTPUT>")] [CommandArgument(0, "<OUTPUT>")]
public string Output { get; set; } public string Output { get; set; }
[CommandOption("-i|--input <PATH>")]
public string Input { get; set; }
} }
} }

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using AngleSharp.Html.Parser;
using Generator.Models;
using Scriban;
using Scriban.Runtime;
using Spectre.Cli;
using Spectre.IO;
using Path = Spectre.IO.Path;
using SpectreEnvironment = Spectre.IO.Environment;
namespace Generator.Commands
{
public sealed class EmojiGeneratorCommand : AsyncCommand<GeneratorCommandSettings>
{
private readonly IFileSystem _fileSystem;
private readonly IEnvironment _environment;
private readonly IHtmlParser _parser;
private readonly Dictionary<string, string> _templates = new Dictionary<string, string>
{
{ "Templates/Emoji.Generated.template", "Emoji.Generated.cs" },
{ "Templates/Emoji.Json.template", "emojis.json" },
};
public EmojiGeneratorCommand()
{
_fileSystem = new FileSystem();
_environment = new SpectreEnvironment();
_parser = new HtmlParser();
}
public override async Task<int> ExecuteAsync(CommandContext context, GeneratorCommandSettings settings)
{
var output = new DirectoryPath(settings.Output);
if (!_fileSystem.Directory.Exists(settings.Output))
{
_fileSystem.Directory.Create(settings.Output);
}
var stream = await FetchEmojis(settings);
var document = await _parser.ParseDocumentAsync(stream);
var emojis = Emoji.Parse(document).OrderBy(x => x.Name)
.Where(emoji => !emoji.HasCombinators)
.ToList();
// Render all templates
foreach (var (templateFilename, outputFilename) in _templates)
{
var result = await RenderTemplate(new FilePath(templateFilename), emojis);
var outputPath = output.CombineWithFilePath(outputFilename);
await File.WriteAllTextAsync(outputPath.FullPath, result);
}
return 0;
}
private async Task<Stream> FetchEmojis(GeneratorCommandSettings settings)
{
var input = string.IsNullOrEmpty(settings.Input)
? _environment.WorkingDirectory
: new DirectoryPath(settings.Input);
var file = _fileSystem.File.Retrieve(input.CombineWithFilePath("emoji-list.html"));
if (!file.Exists)
{
using var http = new HttpClient();
using var httpStream = await http.GetStreamAsync("http://www.unicode.org/emoji/charts/emoji-list.html");
using var outStream = file.OpenWrite();
await httpStream.CopyToAsync(outStream);
}
return file.OpenRead();
}
private static async Task<string> RenderTemplate(Path path, IReadOnlyCollection<Emoji> emojis)
{
var text = await File.ReadAllTextAsync(path.FullPath);
var template = Template.Parse(text);
var templateContext = new TemplateContext
{
// Because of the insane amount of Emojis,
// we need to get rid of some secure defaults :P
LoopLimit = int.MaxValue,
};
var scriptObject = new ScriptObject();
scriptObject.Import(new { Emojis = emojis });
templateContext.PushGlobal(scriptObject);
return await template.RenderAsync(templateContext);
}
}
}

View File

@@ -24,9 +24,17 @@
<None Update="Templates\ColorPalette.Generated.template"> <None Update="Templates\ColorPalette.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Templates\Emoji.Json.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Templates\Emoji.Generated.template">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp" Version="0.14.0" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Scriban" Version="2.1.3" /> <PackageReference Include="Scriban" Version="2.1.3" />
<PackageReference Include="Spectre.Cli" Version="0.36.1-preview.0.6" /> <PackageReference Include="Spectre.Cli" Version="0.36.1-preview.0.6" />

View File

@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using Humanizer;
namespace Generator.Models
{
public class Emoji
{
private static readonly string[] _headers = { "count", "code", "sample", "name" };
private Emoji(string identifier, string name, string code, string description)
{
Identifier = identifier;
Name = name;
Code = code;
Description = description;
NormalizedCode = Code.Replace("\\U", "U+");
HasCombinators = Code.Split(new[] { "\\U" }, System.StringSplitOptions.RemoveEmptyEntries).Length > 1;
}
public string Identifier { get; set; }
public string Code { get; }
public string NormalizedCode { get; }
public string Name { get; }
public string Description { get; set; }
public bool HasCombinators { get; set; }
public static IEnumerable<Emoji> Parse(IHtmlDocument document)
{
var rows = document
.GetNodes<IHtmlTableRowElement>(predicate: row =>
row.Cells.Length >= _headers.Length && // Filter out rows that don't have enough cells.
row.Cells.All(x => x.LocalName == TagNames.Td)); // We're only interested in td cells, not th.
foreach (var row in rows)
{
var dictionary = _headers
.Zip(row.Cells, (header, cell) => (Header: header, cell.TextContent.Trim()))
.ToDictionary(x => x.Item1, x => x.Item2);
var code = TransformCode(dictionary["code"]);
var identifier = TransformName(dictionary["name"])
.Replace("-", "_")
.Replace("(", string.Empty)
.Replace(")", string.Empty);
var description = dictionary["name"].Humanize();
var name = identifier
.Replace("1st", "first")
.Replace("2nd", "second")
.Replace("3rd", "third")
.Pascalize();
yield return new Emoji(identifier, name, code, description);
}
}
private static string TransformName(string name)
{
return name.Replace(":", string.Empty)
.Replace(",", string.Empty)
.Replace(".", string.Empty)
.Replace("\u201c", string.Empty)
.Replace("\u201d", string.Empty)
.Replace("\u229b", string.Empty)
.Replace(' ', '_')
.Replace("s", "s")
.Replace("", "_")
.Replace("&", "and")
.Replace("#", "hash")
.Replace("*", "star")
.Replace("!", string.Empty)
.Trim()
.ToLowerInvariant();
}
private static string TransformCode(string code)
{
var builder = new StringBuilder();
foreach (var part in code.Split(' '))
{
builder.Append(part.Length == 6
? part.Replace("+", "0000")
: part.Replace("+", "000"));
}
return builder.ToString().Replace("U", "\\U");
}
}
}

View File

@@ -11,6 +11,7 @@ namespace Generator
app.Configure(config => app.Configure(config =>
{ {
config.AddCommand<ColorGeneratorCommand>("colors"); config.AddCommand<ColorGeneratorCommand>("colors");
config.AddCommand<EmojiGeneratorCommand>("emoji");
}); });
return app.Run(args); return app.Run(args);

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }} // Generated {{ date.now | date.to_string `%F %R` }}
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }} // Generated {{ date.now | date.to_string `%F %R` }}
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Generated {{ date.now | date.to_string `%Y-%m-%d %k:%M` }} // Generated {{ date.now | date.to_string `%F %R` }}
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.

View File

@@ -0,0 +1,43 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Generated {{ date.now | date.to_string `%F %R` }}
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace Spectre.Console
{
/// <summary>
/// Utility for working with emojis.
/// </summary>
public static partial class Emoji
{
private static readonly Dictionary<string, string> _emojis
= new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
{{~ for emoji in emojis ~}}
{ "{{ emoji.identifier }}", Emoji.Known.{{ emoji.name }} },
{{~ end ~}}
};
/// <summary>
/// Contains all predefined emojis.
/// </summary>
public static class Known
{
{{- for emoji in emojis }}
/// <summary>
/// Gets the "{{ emoji.identifier }}" emoji.
/// Description: {{ emoji.description }}.
/// </summary>
public const string {{ emoji.name }} = "{{ emoji.code }}";
{{~ end ~}}
}
}
}

View File

@@ -0,0 +1,10 @@
[
{{~ for x in 0..(emojis.size-1) ~}}
{
"id": "{{ emojis[x].identifier }}",
"name": "{{ emojis[x].name }}",
"description": "{{ emojis[x].description }}",
"code": "{{ emojis[x].normalized_code }}"
}{{ if x != (emojis.size-1) }},{{ end }}
{{~ end ~}}
]

View File

@@ -1,24 +0,0 @@
using System;
namespace Spectre.Console.Tests
{
public static class ConsoleExtensions
{
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (foreground)
{
console.Foreground = color;
}
else
{
console.Background = color;
}
}
}
}

View File

@@ -0,0 +1,15 @@
namespace Spectre.Console.Tests
{
internal static class StyleExtensions
{
public static Style SetColor(this Style style, Color color, bool foreground)
{
if (foreground)
{
return style.WithForeground(color);
}
return style.WithBackground(color);
}
}
}

View File

@@ -1,32 +0,0 @@
using System;
using System.IO;
namespace Spectre.Console.Tests
{
public sealed class AnsiConsoleFixture : IDisposable
{
private readonly StringWriter _writer;
public IAnsiConsole Console { get; }
public string Output => _writer.ToString();
public AnsiConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
{
_writer = new StringWriter();
Console = new ConsoleWithWidth(
AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = ansi,
ColorSystem = (ColorSystemSupport)system,
Out = _writer,
}), width);
}
public void Dispose()
{
_writer?.Dispose();
}
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using System;
using System.IO;
using System.Text;
using Spectre.Console.Tests.Tools;
namespace Spectre.Console.Tests
{
public sealed class TestableAnsiConsole : IDisposable, IAnsiConsole
{
private readonly StringWriter _writer;
private readonly IAnsiConsole _console;
public string Output => _writer.ToString();
public Capabilities Capabilities => _console.Capabilities;
public Encoding Encoding => _console.Encoding;
public int Width { get; }
public int Height => _console.Height;
public TestableAnsiConsole(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
{
_writer = new StringWriter();
_console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = ansi,
ColorSystem = (ColorSystemSupport)system,
Out = _writer,
LinkIdentityGenerator = new TestLinkIdentityGenerator(),
});
Width = width;
}
public void Dispose()
{
_writer?.Dispose();
}
public void Write(string text, Style style)
{
_console.Write(text, style);
}
}
}

View File

@@ -16,6 +16,7 @@ namespace Spectre.Console.Tests
public Decoration Decoration { get; set; } public Decoration Decoration { get; set; }
public Color Foreground { get; set; } public Color Foreground { get; set; }
public Color Background { get; set; } public Color Background { get; set; }
public string Link { get; set; }
public StringWriter Writer { get; } public StringWriter Writer { get; }
public string RawOutput => Writer.ToString(); public string RawOutput => Writer.ToString();
@@ -39,7 +40,7 @@ namespace Spectre.Console.Tests
Writer.Dispose(); Writer.Dispose();
} }
public void Write(string text) public void Write(string text, Style style)
{ {
Writer.Write(text); Writer.Write(text);
} }

View File

@@ -0,0 +1,10 @@
namespace Spectre.Console.Tests.Tools
{
public sealed class TestLinkIdentityGenerator : ILinkIdentityGenerator
{
public int GenerateId(string link, string text)
{
return 1024;
}
}
}

View File

@@ -13,14 +13,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Return_Correct_Code(bool foreground, string expected) public void Should_Return_Correct_Code(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); var console = new TestableAnsiConsole(ColorSystem.TrueColor);
fixture.Console.SetColor(new Color(128, 0, 128), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(128, 0, 128), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -29,14 +28,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Return_Eight_Bit_Ansi_Code_For_Known_Colors(bool foreground, string expected) public void Should_Return_Eight_Bit_Ansi_Code_For_Known_Colors(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); var console = new TestableAnsiConsole(ColorSystem.TrueColor);
fixture.Console.SetColor(Color.Purple, foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(Color.Purple, foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
} }
@@ -48,14 +46,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected) public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.EightBit); var console = new TestableAnsiConsole(ColorSystem.EightBit);
fixture.Console.SetColor(Color.Olive, foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(Color.Olive, foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -64,14 +61,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Map_TrueColor_To_Nearest_Eight_Bit_Color_If_Possible(bool foreground, string expected) public void Should_Map_TrueColor_To_Nearest_Eight_Bit_Color_If_Possible(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.EightBit); var console = new TestableAnsiConsole(ColorSystem.EightBit);
fixture.Console.SetColor(new Color(128, 128, 0), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(128, 128, 0), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -80,14 +76,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Estimate_TrueColor_To_Nearest_Eight_Bit_Color(bool foreground, string expected) public void Should_Estimate_TrueColor_To_Nearest_Eight_Bit_Color(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.EightBit); var console = new TestableAnsiConsole(ColorSystem.EightBit);
fixture.Console.SetColor(new Color(126, 127, 0), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(126, 127, 0), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
} }
@@ -99,14 +94,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected) public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.SetColor(Color.Olive, foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(Color.Olive, foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -120,14 +114,13 @@ namespace Spectre.Console.Tests.Unit
string expected) string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.SetColor(new Color(r, g, b), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -141,14 +134,13 @@ namespace Spectre.Console.Tests.Unit
string expected) string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.SetColor(new Color(r, g, b), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
} }
@@ -160,14 +152,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected) public void Should_Return_Correct_Code_For_Known_Color(bool foreground, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Legacy); var console = new TestableAnsiConsole(ColorSystem.Legacy);
fixture.Console.SetColor(Color.Olive, foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(Color.Olive, foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -181,14 +172,13 @@ namespace Spectre.Console.Tests.Unit
string expected) string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Legacy); var console = new TestableAnsiConsole(ColorSystem.Legacy);
fixture.Console.SetColor(new Color(r, g, b), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -202,14 +192,13 @@ namespace Spectre.Console.Tests.Unit
string expected) string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Legacy); var console = new TestableAnsiConsole(ColorSystem.Legacy);
fixture.Console.SetColor(new Color(r, g, b), foreground);
// When // When
fixture.Console.Write("Hello"); console.Write("Hello", new Style().SetColor(new Color(r, g, b), foreground));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
} }
} }

View File

@@ -13,16 +13,18 @@ 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!")]
[InlineData("[link=https://patriksvensson.se]Click to visit my blog[/]", "]8;id=1024;https://patriksvensson.se\\Click to visit my blog]8;;\\")]
[InlineData("[link]https://patriksvensson.se[/]", "]8;id=1024;https://patriksvensson.se\\https://patriksvensson.se]8;;\\")]
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
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
fixture.Console.Markup(markup); console.Markup(markup);
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -30,13 +32,13 @@ namespace Spectre.Console.Tests.Unit
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
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
fixture.Console.Markup(markup); console.Markup(markup);
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -47,10 +49,10 @@ namespace Spectre.Console.Tests.Unit
public void Should_Throw_If_Encounters_Malformed_Tag(string markup, string expected) public void Should_Throw_If_Encounters_Malformed_Tag(string markup, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
var result = Record.Exception(() => fixture.Console.Markup(markup)); var result = Record.Exception(() => console.Markup(markup));
// Then // Then
result.ShouldBeOfType<InvalidOperationException>() result.ShouldBeOfType<InvalidOperationException>()
@@ -61,10 +63,10 @@ namespace Spectre.Console.Tests.Unit
public void Should_Throw_If_Tags_Are_Unbalanced() public void Should_Throw_If_Tags_Are_Unbalanced()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
var result = Record.Exception(() => fixture.Console.Markup("[yellow][blue]Hello[/]")); var result = Record.Exception(() => console.Markup("[yellow][blue]Hello[/]"));
// Then // Then
result.ShouldBeOfType<InvalidOperationException>() result.ShouldBeOfType<InvalidOperationException>()
@@ -75,10 +77,10 @@ namespace Spectre.Console.Tests.Unit
public void Should_Throw_If_Encounters_Closing_Tag() public void Should_Throw_If_Encounters_Closing_Tag()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
var result = Record.Exception(() => fixture.Console.Markup("Hello[/]World")); var result = Record.Exception(() => console.Markup("Hello[/]World"));
// Then // Then
result.ShouldBeOfType<InvalidOperationException>() result.ShouldBeOfType<InvalidOperationException>()

View File

@@ -18,14 +18,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected) public void Should_Write_Decorated_Text_Correctly(Decoration decoration, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); var console = new TestableAnsiConsole(ColorSystem.TrueColor);
fixture.Console.Decoration = decoration;
// When // When
fixture.Console.Write("Hello World"); console.Write("Hello World", Style.WithDecoration(decoration));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
[Theory] [Theory]
@@ -34,14 +33,13 @@ namespace Spectre.Console.Tests.Unit
public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected) public void Should_Write_Text_With_Multiple_Decorations_Correctly(Decoration decoration, string expected)
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.TrueColor); var console = new TestableAnsiConsole(ColorSystem.TrueColor);
fixture.Console.Decoration = decoration;
// When // When
fixture.Console.Write("Hello World"); console.Write("Hello World", Style.WithDecoration(decoration));
// Then // Then
fixture.Output.ShouldBe(expected); console.Output.ShouldBe(expected);
} }
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Globalization;
using Shouldly; using Shouldly;
using Xunit; using Xunit;
@@ -11,237 +10,68 @@ namespace Spectre.Console.Tests.Unit
public void Should_Combine_Decoration_And_Colors() public void Should_Combine_Decoration_And_Colors()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Decoration = Decoration.Italic;
// When // When
fixture.Console.Write("Hello"); console.Write(
"Hello",
Style.WithForeground(Color.RoyalBlue1)
.WithBackground(Color.NavajoWhite1)
.WithDecoration(Decoration.Italic));
// Then // Then
fixture.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m"); console.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m");
} }
[Fact] [Fact]
public void Should_Not_Include_Foreground_If_Set_To_Default_Color() public void Should_Not_Include_Foreground_If_Set_To_Default_Color()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.Foreground = Color.Default;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Decoration = Decoration.Italic;
// When // When
fixture.Console.Write("Hello"); console.Write(
"Hello",
Style.WithForeground(Color.Default)
.WithBackground(Color.NavajoWhite1)
.WithDecoration(Decoration.Italic));
// Then // Then
fixture.Output.ShouldBe("\u001b[3;47mHello\u001b[0m"); console.Output.ShouldBe("\u001b[3;47mHello\u001b[0m");
} }
[Fact] [Fact]
public void Should_Not_Include_Background_If_Set_To_Default_Color() public void Should_Not_Include_Background_If_Set_To_Default_Color()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.Default;
fixture.Console.Decoration = Decoration.Italic;
// When // When
fixture.Console.Write("Hello"); console.Write(
"Hello",
Style.WithForeground(Color.RoyalBlue1)
.WithBackground(Color.Default)
.WithDecoration(Decoration.Italic));
// Then // Then
fixture.Output.ShouldBe("\u001b[3;90mHello\u001b[0m"); console.Output.ShouldBe("\u001b[3;90mHello\u001b[0m");
} }
[Fact] [Fact]
public void Should_Not_Include_Decoration_If_Set_To_None() public void Should_Not_Include_Decoration_If_Set_To_None()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard); var console = new TestableAnsiConsole(ColorSystem.Standard);
fixture.Console.Foreground = Color.RoyalBlue1;
fixture.Console.Background = Color.NavajoWhite1;
fixture.Console.Decoration = Decoration.None;
// When // When
fixture.Console.Write("Hello"); console.Write(
"Hello",
Style.WithForeground(Color.RoyalBlue1)
.WithBackground(Color.NavajoWhite1)
.WithDecoration(Decoration.None));
// Then // Then
fixture.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m");
}
public sealed class Write
{
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Int32_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 32);
// Then
fixture.Output.ShouldBe("32");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_UInt32_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 32U);
// Then
fixture.Output.ShouldBe("32");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Int64_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 32L);
// Then
fixture.Output.ShouldBe("32");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_UInt64_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 32UL);
// Then
fixture.Output.ShouldBe("32");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Single_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 32.432F);
// Then
fixture.Output.ShouldBe("32.432");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Double_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, (double)32.432);
// Then
fixture.Output.ShouldBe("32.432");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Decimal_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 32.432M);
// Then
fixture.Output.ShouldBe("32.432");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Boolean_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, true);
// Then
fixture.Output.ShouldBe("True");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Char_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(CultureInfo.InvariantCulture, 'P');
// Then
fixture.Output.ShouldBe("P");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Char_Array_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(
CultureInfo.InvariantCulture,
new[] { 'P', 'a', 't', 'r', 'i', 'k' });
// Then
fixture.Output.ShouldBe("Patrik");
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Formatted_String_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.Write(
CultureInfo.InvariantCulture,
"Hello {0}! {1}",
"World", 32);
// Then
fixture.Output.ShouldBe("Hello World! 32");
}
} }
public sealed class WriteLine public sealed class WriteLine
@@ -250,16 +80,14 @@ namespace Spectre.Console.Tests.Unit
public void Should_Reset_Colors_Correctly_After_Line_Break() public void Should_Reset_Colors_Correctly_After_Line_Break()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
fixture.Console.Background = ConsoleColor.Red; console.WriteLine("Hello", Style.WithBackground(ConsoleColor.Red));
fixture.Console.WriteLine("Hello"); console.WriteLine("World", Style.WithBackground(ConsoleColor.Green));
fixture.Console.Background = ConsoleColor.Green;
fixture.Console.WriteLine("World");
// Then // Then
fixture.Output.NormalizeLineEndings() console.Output.NormalizeLineEndings()
.ShouldBe("Hello\nWorld\n"); .ShouldBe("Hello\nWorld\n");
} }
@@ -267,186 +95,15 @@ namespace Spectre.Console.Tests.Unit
public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text() public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text()
{ {
// Given // Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When // When
fixture.Console.Background = ConsoleColor.Red; console.WriteLine("Hello\nWorld", Style.WithBackground(ConsoleColor.Red));
fixture.Console.WriteLine("Hello\nWorld");
// Then // Then
fixture.Output.NormalizeLineEndings() console.Output.NormalizeLineEndings()
.ShouldBe("Hello\nWorld\n"); .ShouldBe("Hello\nWorld\n");
} }
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Int32_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32);
// Then
fixture.Output.ShouldBe("32" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_UInt32_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32U);
// Then
fixture.Output.ShouldBe("32" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Int64_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32L);
// Then
fixture.Output.ShouldBe("32" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_UInt64_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32UL);
// Then
fixture.Output.ShouldBe("32" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Single_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32.432F);
// Then
fixture.Output.ShouldBe("32.432" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Double_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, (double)32.432);
// Then
fixture.Output.ShouldBe("32.432" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Decimal_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 32.432M);
// Then
fixture.Output.ShouldBe("32.432" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Boolean_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, true);
// Then
fixture.Output.ShouldBe("True" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Char_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(CultureInfo.InvariantCulture, 'P');
// Then
fixture.Output.ShouldBe("P" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Char_Array_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(
CultureInfo.InvariantCulture,
new[] { 'P', 'a', 't', 'r', 'i', 'k' });
// Then
fixture.Output.ShouldBe("Patrik" + Environment.NewLine);
}
[Theory]
[InlineData(AnsiSupport.Yes)]
[InlineData(AnsiSupport.No)]
public void Should_Write_Formatted_String_With_Format_Provider(AnsiSupport ansi)
{
// Given
var fixture = new AnsiConsoleFixture(ColorSystem.Standard, ansi);
// When
fixture.Console.WriteLine(
CultureInfo.InvariantCulture,
"Hello {0}! {1}",
"World", 32);
// Then
fixture.Output.ShouldBe("Hello World! 32" + Environment.NewLine);
}
} }
} }
} }

View File

@@ -0,0 +1,44 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class EmojiTests
{
[Fact]
public void Should_Substitute_Emoji_Shortcodes_In_Markdown()
{
// Given
var console = new TestableAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
// When
console.Markup("Hello :globe_showing_europe_africa:!");
// Then
console.Output.ShouldBe("Hello 🌍!");
}
[Fact]
public void Should_Contain_Predefined_Emojis()
{
// Given, When
const string result = "Hello " + Emoji.Known.GlobeShowingEuropeAfrica + "!";
// Then
result.ShouldBe("Hello 🌍!");
}
public sealed class TheReplaceMethod
{
[Fact]
public void Should_Replace_Emojis_In_Text()
{
// Given, When
var result = Emoji.Replace("Hello :globe_showing_europe_africa:!");
// Then
result.ShouldBe("Hello 🌍!");
}
}
}
}

View File

@@ -190,9 +190,9 @@ namespace Spectre.Console.Tests.Unit
// Given // Given
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var grid = new Grid(); var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(3, 0) }); grid.AddColumn(new GridColumn { Padding = new Padding(3, 0, 0, 0) });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) }); grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 0, 0) });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 3) }); grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 3, 0) });
grid.AddRow("Foo", "Bar", "Baz"); grid.AddRow("Foo", "Bar", "Baz");
grid.AddRow("Qux", "Corgi", "Waldo"); grid.AddRow("Qux", "Corgi", "Waldo");
grid.AddRow("Grault", "Garply", "Fred"); grid.AddRow("Grault", "Garply", "Fred");
@@ -213,7 +213,7 @@ namespace Spectre.Console.Tests.Unit
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var grid = new Grid(); var grid = new Grid();
grid.AddColumn(new GridColumn { NoWrap = true }); grid.AddColumn(new GridColumn { NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) }); grid.AddColumn(new GridColumn { Padding = new Padding(2, 0, 0, 0) });
grid.AddRow("[bold]Options[/]", string.Empty); grid.AddRow("[bold]Options[/]", string.Empty);
grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help."); grid.AddRow(" [blue]-h[/], [blue]--help[/]", "Show command line help.");
grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/]."); grid.AddRow(" [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/].");

View File

@@ -12,7 +12,7 @@ namespace Spectre.Console.Tests.Unit
public void Should_Throw_If_Closing_Tag_Is_Not_Properly_Escaped(string input) public void Should_Throw_If_Closing_Tag_Is_Not_Properly_Escaped(string input)
{ {
// Given // Given
var fixture = new PlainConsole(); var console = new PlainConsole();
// When // When
var result = Record.Exception(() => new Markup(input)); var result = Record.Exception(() => new Markup(input));
@@ -27,14 +27,30 @@ namespace Spectre.Console.Tests.Unit
public void Should_Escape_Markup_Blocks_As_Expected() public void Should_Escape_Markup_Blocks_As_Expected()
{ {
// Given // Given
var fixture = new PlainConsole(); var console = new PlainConsole();
var markup = new Markup("Hello [[ World ]] !"); var markup = new Markup("Hello [[ World ]] !");
// When // When
fixture.Render(markup); console.Render(markup);
// Then // Then
fixture.Output.ShouldBe("Hello [ World ] !"); console.Output.ShouldBe("Hello [ World ] !");
}
[Theory]
[InlineData("Hello [link=http://example.com]example.com[/]", "Hello example.com")]
[InlineData("Hello [link=http://example.com]http://example.com[/]", "Hello http://example.com")]
public void Should_Render_Links_As_Expected(string input, string output)
{
// Given
var console = new PlainConsole();
var markup = new Markup(input);
// When
console.Render(markup);
// Then
console.Output.ShouldBe(output);
} }
} }
} }

View File

@@ -0,0 +1,107 @@
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class PadderTests
{
[Fact]
public void Should_Render_Padded_Object_Correctly()
{
// Given
var console = new PlainConsole(width: 60);
var table = new Table();
table.AddColumn("Foo");
table.AddColumn("Bar");
table.AddRow("Baz", "Qux");
table.AddRow("Corgi", "Waldo");
// When
console.Render(new Padder(table).SetPadding(1, 2, 3, 4));
// Then
console.Lines.Count.ShouldBe(12);
console.Lines[00].ShouldBe(" ");
console.Lines[01].ShouldBe(" ");
console.Lines[02].ShouldBe(" ┌───────┬───────┐ ");
console.Lines[03].ShouldBe(" │ Foo │ Bar │ ");
console.Lines[04].ShouldBe(" ├───────┼───────┤ ");
console.Lines[05].ShouldBe(" │ Baz │ Qux │ ");
console.Lines[06].ShouldBe(" │ Corgi │ Waldo │ ");
console.Lines[07].ShouldBe(" └───────┴───────┘ ");
console.Lines[08].ShouldBe(" ");
console.Lines[09].ShouldBe(" ");
console.Lines[10].ShouldBe(" ");
console.Lines[11].ShouldBe(" ");
}
[Fact]
public void Should_Render_Expanded_Padded_Object_Correctly()
{
// Given
var console = new PlainConsole(width: 60);
var table = new Table();
table.AddColumn("Foo");
table.AddColumn("Bar");
table.AddRow("Baz", "Qux");
table.AddRow("Corgi", "Waldo");
// When
console.Render(new Padder(table)
.SetPadding(1, 2, 3, 4)
.Expand());
// Then
console.Lines.Count.ShouldBe(12);
console.Lines[00].ShouldBe(" ");
console.Lines[01].ShouldBe(" ");
console.Lines[02].ShouldBe(" ┌───────┬───────┐ ");
console.Lines[03].ShouldBe(" │ Foo │ Bar │ ");
console.Lines[04].ShouldBe(" ├───────┼───────┤ ");
console.Lines[05].ShouldBe(" │ Baz │ Qux │ ");
console.Lines[06].ShouldBe(" │ Corgi │ Waldo │ ");
console.Lines[07].ShouldBe(" └───────┴───────┘ ");
console.Lines[08].ShouldBe(" ");
console.Lines[09].ShouldBe(" ");
console.Lines[10].ShouldBe(" ");
console.Lines[11].ShouldBe(" ");
}
[Fact]
public void Should_Render_Padded_Object_Correctly_When_Nested_Within_Other_Object()
{
// Given
var console = new PlainConsole(width: 60);
var table = new Table();
table.AddColumn("Foo");
table.AddColumn("Bar", c => c.PadLeft(0).PadRight(0));
table.AddRow("Baz", "Qux");
table.AddRow(new Text("Corgi"), new Padder(new Panel("Waldo"))
.SetPadding(2, 1, 2, 1));
// When
console.Render(new Padder(table)
.SetPadding(1, 2, 3, 4)
.Expand());
// Then
console.Lines.Count.ShouldBe(16);
console.Lines[00].ShouldBe(" ");
console.Lines[01].ShouldBe(" ");
console.Lines[02].ShouldBe(" ┌───────┬─────────────┐ ");
console.Lines[03].ShouldBe(" │ Foo │Bar │ ");
console.Lines[04].ShouldBe(" ├───────┼─────────────┤ ");
console.Lines[05].ShouldBe(" │ Baz │Qux │ ");
console.Lines[06].ShouldBe(" │ Corgi │ │ ");
console.Lines[07].ShouldBe(" │ │ ┌───────┐ │ ");
console.Lines[08].ShouldBe(" │ │ │ Waldo │ │ ");
console.Lines[09].ShouldBe(" │ │ └───────┘ │ ");
console.Lines[10].ShouldBe(" │ │ │ ");
console.Lines[11].ShouldBe(" └───────┴─────────────┘ ");
console.Lines[12].ShouldBe(" ");
console.Lines[13].ShouldBe(" ");
console.Lines[14].ShouldBe(" ");
console.Lines[15].ShouldBe(" ");
}
}
}

View File

@@ -21,6 +21,25 @@ namespace Spectre.Console.Tests.Unit
console.Lines[2].ShouldBe("└─────────────┘"); console.Lines[2].ShouldBe("└─────────────┘");
} }
[Fact]
public void Should_Render_Panel_With_Padding_Set_To_Zero()
{
// Given
var console = new PlainConsole(width: 80);
// When
console.Render(new Panel(new Text("Hello World"))
{
Padding = new Padding(0, 0, 0, 0),
});
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("┌───────────┐");
console.Lines[1].ShouldBe("│Hello World│");
console.Lines[2].ShouldBe("└───────────┘");
}
[Fact] [Fact]
public void Should_Render_Panel_With_Padding() public void Should_Render_Panel_With_Padding()
{ {
@@ -30,14 +49,17 @@ namespace Spectre.Console.Tests.Unit
// When // When
console.Render(new Panel(new Text("Hello World")) console.Render(new Panel(new Text("Hello World"))
{ {
Padding = new Padding(3, 5), Padding = new Padding(3, 1, 5, 2),
}); });
// Then // Then
console.Lines.Count.ShouldBe(3); console.Lines.Count.ShouldBe(6);
console.Lines[0].ShouldBe("┌───────────────────┐"); console.Lines[0].ShouldBe("┌───────────────────┐");
console.Lines[1].ShouldBe("│ Hello World │"); console.Lines[1].ShouldBe("│ │");
console.Lines[2].ShouldBe("└───────────────────┘"); console.Lines[2].ShouldBe("│ Hello World │");
console.Lines[3].ShouldBe("│ │");
console.Lines[4].ShouldBe("│ │");
console.Lines[5].ShouldBe("└───────────────────┘");
} }
[Fact] [Fact]
@@ -51,7 +73,7 @@ namespace Spectre.Console.Tests.Unit
{ {
Header = new PanelHeader("Greeting"), Header = new PanelHeader("Greeting"),
Expand = true, Expand = true,
Padding = new Padding(2, 2), Padding = new Padding(2, 0, 2, 0),
}); });
// Then // Then

View File

@@ -11,7 +11,7 @@ namespace Spectre.Console.Tests.Unit
{ {
// Given // Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic); var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic);
var other = new Style(Color.Green, Color.Silver, Decoration.Underline); var other = new Style(Color.Green, Color.Silver, Decoration.Underline, "https://example.com");
// When // When
var result = first.Combine(other); var result = first.Combine(other);
@@ -20,6 +20,77 @@ namespace Spectre.Console.Tests.Unit
result.Foreground.ShouldBe(Color.Green); result.Foreground.ShouldBe(Color.Green);
result.Background.ShouldBe(Color.Silver); result.Background.ShouldBe(Color.Silver);
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline); result.Decoration.ShouldBe(Decoration.Bold | Decoration.Italic | Decoration.Underline);
result.Link.ShouldBe("https://example.com");
}
[Fact]
public void Should_Consider_Two_Identical_Styles_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeTrue();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Foreground_Colors_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.Blue, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Background_Colors_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Blue, Decoration.Bold | Decoration.Italic, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Decorations_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Yellow, Decoration.Bold, "http://example.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
}
[Fact]
public void Should_Not_Consider_Two_Styles_With_Different_Links_Equal()
{
// Given
var first = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://example.com");
var second = new Style(Color.White, Color.Yellow, Decoration.Bold | Decoration.Italic, "http://foo.com");
// When
var result = first.Equals(second);
// Then
result.ShouldBeFalse();
} }
public sealed class TheParseMethod public sealed class TheParseMethod
@@ -62,16 +133,36 @@ namespace Spectre.Console.Tests.Unit
} }
[Fact] [Fact]
public void Should_Parse_Text_And_Decoration() public void Should_Parse_Link_Without_Address()
{ {
// Given, When // Given, When
var result = Style.Parse("bold underline blue on green"); var result = Style.Parse("link");
// Then // Then
result.ShouldNotBeNull(); result.ShouldNotBeNull();
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline); result.Link.ShouldBe("https://emptylink");
result.Foreground.ShouldBe(Color.Blue); }
result.Background.ShouldBe(Color.Green);
[Fact]
public void Should_Parse_Link()
{
// Given, When
var result = Style.Parse("link=https://example.com");
// Then
result.ShouldNotBeNull();
result.Link.ShouldBe("https://example.com");
}
[Fact]
public void Should_Throw_If_Link_Is_Set_Twice()
{
// Given, When
var result = Record.Exception(() => Style.Parse("link=https://example.com link=https://example.com"));
// Then
result.ShouldBeOfType<InvalidOperationException>();
result.Message.ShouldBe("A link has already been set.");
} }
[Fact] [Fact]
@@ -131,6 +222,20 @@ namespace Spectre.Console.Tests.Unit
result.Message.ShouldBe("Could not find color 'lol'."); result.Message.ShouldBe("Could not find color 'lol'.");
} }
[Fact]
public void Should_Parse_Colors_And_Decoration_And_Link()
{
// Given, When
var result = Style.Parse("link=https://example.com bold underline blue on green");
// Then
result.ShouldNotBeNull();
result.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline);
result.Foreground.ShouldBe(Color.Blue);
result.Background.ShouldBe(Color.Green);
result.Link.ShouldBe("https://example.com");
}
[Theory] [Theory]
[InlineData("#FF0000 on #0000FF")] [InlineData("#FF0000 on #0000FF")]
[InlineData("#F00 on #00F")] [InlineData("#F00 on #00F")]

View File

@@ -347,7 +347,7 @@ namespace Spectre.Console.Tests.Unit
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var table = new Table(); var table = new Table();
table.AddColumns("Foo", "Bar"); table.AddColumns("Foo", "Bar");
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) }); table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 0, 2, 0) });
table.AddRow("Qux\nQuuux", "Corgi", "Waldo"); table.AddRow("Qux\nQuuux", "Corgi", "Waldo");
table.AddRow("Grault", "Garply", "Fred"); table.AddRow("Grault", "Garply", "Fred");
@@ -372,7 +372,7 @@ namespace Spectre.Console.Tests.Unit
var console = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var table = new Table(); var table = new Table();
table.AddColumns("Foo", "Bar"); table.AddColumns("Foo", "Bar");
table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) }); table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 0, 2, 0) });
// When // When
console.Render(table); console.Render(table);

View File

@@ -37,14 +37,14 @@ namespace Spectre.Console.Tests.Unit
public void Should_Render_Unstyled_Text_As_Expected() public void Should_Render_Unstyled_Text_As_Expected()
{ {
// Given // Given
var fixture = new PlainConsole(width: 80); var console = new PlainConsole(width: 80);
var text = new Text("Hello World"); var text = new Text("Hello World");
// When // When
fixture.Render(text); console.Render(text);
// Then // Then
fixture.Output console.Output
.NormalizeLineEndings() .NormalizeLineEndings()
.ShouldBe("Hello World"); .ShouldBe("Hello World");
} }
@@ -55,14 +55,14 @@ namespace Spectre.Console.Tests.Unit
public void Should_Write_Line_Breaks(string input) public void Should_Write_Line_Breaks(string input)
{ {
// Given // Given
var fixture = new PlainConsole(width: 5); var console = new PlainConsole(width: 5);
var text = new Text(input); var text = new Text(input);
// When // When
fixture.Render(text); console.Render(text);
// Then // Then
fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n"); console.RawOutput.ShouldBe("Hello\n\nWorld\n\n");
} }
[Fact] [Fact]
@@ -87,14 +87,14 @@ namespace Spectre.Console.Tests.Unit
int width, string input, string expected) int width, string input, string expected)
{ {
// Given // Given
var fixture = new PlainConsole(width); var console = new PlainConsole(width);
var text = new Text(input); var text = new Text(input);
// When // When
fixture.Render(text); console.Render(text);
// Then // Then
fixture.Output console.Output
.NormalizeLineEndings() .NormalizeLineEndings()
.ShouldBe(expected); .ShouldBe(expected);
} }
@@ -106,15 +106,15 @@ namespace Spectre.Console.Tests.Unit
public void Should_Overflow_Text_Correctly(Overflow overflow, string expected) public void Should_Overflow_Text_Correctly(Overflow overflow, string expected)
{ {
// Given // Given
var fixture = new PlainConsole(14); var console = new PlainConsole(14);
var text = new Text("foo pneumonoultramicroscopicsilicovolcanoconiosis bar qux") var text = new Text("foo pneumonoultramicroscopicsilicovolcanoconiosis bar qux")
.SetOverflow(overflow); .SetOverflow(overflow);
// When // When
fixture.Render(text); console.Render(text);
// Then // Then
fixture.Output console.Output
.NormalizeLineEndings() .NormalizeLineEndings()
.ShouldBe(expected); .ShouldBe(expected);
} }

View File

@@ -29,7 +29,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Columns", "..\examples\Colu
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Info", "..\examples\Info\Info.csproj", "{225CE0D4-06AB-411A-8D29-707504FE53B3}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Info", "..\examples\Info\Info.csproj", "{225CE0D4-06AB-411A-8D29-707504FE53B3}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Borders", "..\examples\Borders\Borders.csproj", "{094245E6-4C94-485D-B5AC-3153E878B112}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Borders", "..\examples\Borders\Borders.csproj", "{094245E6-4C94-485D-B5AC-3153E878B112}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Links", "..\examples\Links\Links.csproj", "{6AF8C93B-AA41-4F44-8B1B-B8D166576174}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emojis", "..\examples\Emojis\Emojis.csproj", "{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -149,6 +153,30 @@ Global
{094245E6-4C94-485D-B5AC-3153E878B112}.Release|x64.Build.0 = Release|Any CPU {094245E6-4C94-485D-B5AC-3153E878B112}.Release|x64.Build.0 = Release|Any CPU
{094245E6-4C94-485D-B5AC-3153E878B112}.Release|x86.ActiveCfg = Release|Any CPU {094245E6-4C94-485D-B5AC-3153E878B112}.Release|x86.ActiveCfg = Release|Any CPU
{094245E6-4C94-485D-B5AC-3153E878B112}.Release|x86.Build.0 = Release|Any CPU {094245E6-4C94-485D-B5AC-3153E878B112}.Release|x86.Build.0 = Release|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x64.ActiveCfg = Debug|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x64.Build.0 = Debug|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x86.ActiveCfg = Debug|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Debug|x86.Build.0 = Debug|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|Any CPU.Build.0 = Release|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x64.ActiveCfg = Release|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x64.Build.0 = Release|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x86.ActiveCfg = Release|Any CPU
{6AF8C93B-AA41-4F44-8B1B-B8D166576174}.Release|x86.Build.0 = Release|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x64.ActiveCfg = Debug|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x64.Build.0 = Debug|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x86.ActiveCfg = Debug|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Debug|x86.Build.0 = Debug|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|Any CPU.Build.0 = Release|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x64.ActiveCfg = Release|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x64.Build.0 = Release|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x86.ActiveCfg = Release|Any CPU
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -161,6 +189,8 @@ Global
{33357599-C79D-4299-888F-634E2C3EACEF} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {33357599-C79D-4299-888F-634E2C3EACEF} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{225CE0D4-06AB-411A-8D29-707504FE53B3} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {225CE0D4-06AB-411A-8D29-707504FE53B3} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{094245E6-4C94-485D-B5AC-3153E878B112} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {094245E6-4C94-485D-B5AC-3153E878B112} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{6AF8C93B-AA41-4F44-8B1B-B8D166576174} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2} = {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

@@ -0,0 +1,85 @@
using System;
using System.IO;
using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
private static ConsoleColor _defaultForeground;
private static ConsoleColor _defaultBackground;
internal static Style CurrentStyle { get; private set; } = Style.Plain;
internal static bool Created { get; private set; }
/// <summary>
/// Gets or sets the foreground color.
/// </summary>
public static Color Foreground
{
get => CurrentStyle.Foreground;
set => CurrentStyle = CurrentStyle.WithForeground(value);
}
/// <summary>
/// Gets or sets the background color.
/// </summary>
public static Color Background
{
get => CurrentStyle.Background;
set => CurrentStyle = CurrentStyle.WithBackground(value);
}
/// <summary>
/// Gets or sets the text decoration.
/// </summary>
public static Decoration Decoration
{
get => CurrentStyle.Decoration;
set => CurrentStyle = CurrentStyle.WithDecoration(value);
}
internal static void Initialize(TextWriter? @out)
{
if (@out?.IsStandardOut() ?? false)
{
Foreground = _defaultForeground = System.Console.ForegroundColor;
Background = _defaultBackground = System.Console.BackgroundColor;
}
else
{
Foreground = _defaultForeground = Color.Silver;
Background = _defaultBackground = Color.Black;
}
}
/// <summary>
/// Resets colors and text decorations.
/// </summary>
public static void Reset()
{
ResetColors();
ResetDecoration();
}
/// <summary>
/// Resets the current applied text decorations.
/// </summary>
public static void ResetDecoration()
{
Decoration = Decoration.None;
}
/// <summary>
/// Resets the current applied foreground and background colors.
/// </summary>
public static void ResetColors()
{
Foreground = _defaultForeground;
Background = _defaultBackground;
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(string value) public static void Write(string value)
{ {
Console.Write(value); Console.Write(value, CurrentStyle);
} }
/// <summary> /// <summary>
@@ -24,7 +24,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(int value) public static void Write(int value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -35,7 +35,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, int value) public static void Write(IFormatProvider provider, int value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -45,7 +45,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(uint value) public static void Write(uint value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -56,7 +56,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, uint value) public static void Write(IFormatProvider provider, uint value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -66,7 +66,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(long value) public static void Write(long value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -77,7 +77,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, long value) public static void Write(IFormatProvider provider, long value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -87,7 +87,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(ulong value) public static void Write(ulong value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -98,7 +98,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, ulong value) public static void Write(IFormatProvider provider, ulong value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -108,7 +108,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(float value) public static void Write(float value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -119,7 +119,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, float value) public static void Write(IFormatProvider provider, float value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -129,7 +129,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(double value) public static void Write(double value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -140,7 +140,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, double value) public static void Write(IFormatProvider provider, double value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -149,7 +149,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(decimal value) public static void Write(decimal value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -159,7 +159,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, decimal value) public static void Write(IFormatProvider provider, decimal value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -168,7 +168,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(bool value) public static void Write(bool value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -178,7 +178,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, bool value) public static void Write(IFormatProvider provider, bool value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -187,7 +187,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(char value) public static void Write(char value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -197,7 +197,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, char value) public static void Write(IFormatProvider provider, char value)
{ {
Console.Write(value.ToString(provider)); Console.Write(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -206,7 +206,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(char[] value) public static void Write(char[] value)
{ {
Console.Write(CultureInfo.CurrentCulture, value); Write(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -216,7 +216,15 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, char[] value) public static void Write(IFormatProvider provider, char[] value)
{ {
Console.Write(provider, value); if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
Console.Write(value[index].ToString(provider), CurrentStyle);
}
} }
/// <summary> /// <summary>
@@ -227,7 +235,7 @@ namespace Spectre.Console
/// <param name="args">An array of objects to write.</param> /// <param name="args">An array of objects to write.</param>
public static void Write(string format, params object[] args) public static void Write(string format, params object[] args)
{ {
Console.Write(CultureInfo.CurrentCulture, format, args); Write(CultureInfo.CurrentCulture, format, args);
} }
/// <summary> /// <summary>
@@ -239,7 +247,7 @@ namespace Spectre.Console
/// <param name="args">An array of objects to write.</param> /// <param name="args">An array of objects to write.</param>
public static void Write(IFormatProvider provider, string format, params object[] args) public static void Write(IFormatProvider provider, string format, params object[] args)
{ {
Console.Write(string.Format(provider, format, args)); Console.Write(string.Format(provider, format, args), CurrentStyle);
} }
} }
} }

View File

@@ -22,7 +22,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(string value) public static void WriteLine(string value)
{ {
Console.WriteLine(value); Console.WriteLine(value, CurrentStyle);
} }
/// <summary> /// <summary>
@@ -32,7 +32,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(int value) public static void WriteLine(int value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -43,7 +43,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, int value) public static void WriteLine(IFormatProvider provider, int value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -53,7 +53,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(uint value) public static void WriteLine(uint value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -64,7 +64,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, uint value) public static void WriteLine(IFormatProvider provider, uint value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -74,7 +74,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(long value) public static void WriteLine(long value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -85,7 +85,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, long value) public static void WriteLine(IFormatProvider provider, long value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -95,7 +95,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(ulong value) public static void WriteLine(ulong value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -106,7 +106,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, ulong value) public static void WriteLine(IFormatProvider provider, ulong value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -116,7 +116,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(float value) public static void WriteLine(float value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -127,7 +127,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, float value) public static void WriteLine(IFormatProvider provider, float value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -137,7 +137,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(double value) public static void WriteLine(double value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -148,7 +148,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, double value) public static void WriteLine(IFormatProvider provider, double value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -158,7 +158,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(decimal value) public static void WriteLine(decimal value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -169,7 +169,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, decimal value) public static void WriteLine(IFormatProvider provider, decimal value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -179,7 +179,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(bool value) public static void WriteLine(bool value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -190,7 +190,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, bool value) public static void WriteLine(IFormatProvider provider, bool value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -200,7 +200,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(char value) public static void WriteLine(char value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -211,7 +211,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, char value) public static void WriteLine(IFormatProvider provider, char value)
{ {
Console.WriteLine(value.ToString(provider)); Console.WriteLine(value.ToString(provider), CurrentStyle);
} }
/// <summary> /// <summary>
@@ -221,7 +221,7 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(char[] value) public static void WriteLine(char[] value)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, value); WriteLine(CultureInfo.CurrentCulture, value);
} }
/// <summary> /// <summary>
@@ -232,7 +232,17 @@ namespace Spectre.Console
/// <param name="value">The value to write.</param> /// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, char[] value) public static void WriteLine(IFormatProvider provider, char[] value)
{ {
Console.WriteLine(provider, value); if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
Console.Write(value[index].ToString(provider), CurrentStyle);
}
Console.WriteLine();
} }
/// <summary> /// <summary>
@@ -244,7 +254,7 @@ namespace Spectre.Console
/// <param name="args">An array of objects to write.</param> /// <param name="args">An array of objects to write.</param>
public static void WriteLine(string format, params object[] args) public static void WriteLine(string format, params object[] args)
{ {
Console.WriteLine(CultureInfo.CurrentCulture, format, args); WriteLine(CultureInfo.CurrentCulture, format, args);
} }
/// <summary> /// <summary>
@@ -257,7 +267,7 @@ namespace Spectre.Console
/// <param name="args">An array of objects to write.</param> /// <param name="args">An array of objects to write.</param>
public static void WriteLine(IFormatProvider provider, string format, params object[] args) public static void WriteLine(IFormatProvider provider, string format, params object[] args)
{ {
Console.WriteLine(string.Format(provider, format, args)); Console.WriteLine(string.Format(provider, format, args), CurrentStyle);
} }
} }
} }

View File

@@ -16,12 +16,13 @@ namespace Spectre.Console
ColorSystem = ColorSystemSupport.Detect, ColorSystem = ColorSystemSupport.Detect,
Out = System.Console.Out, Out = System.Console.Out,
}); });
Initialize(System.Console.Out);
Created = true; Created = true;
return console; return console;
}); });
/// <summary> /// <summary>
/// Gets the current renderer. /// Gets the underlying <see cref="IAnsiConsole"/>.
/// </summary> /// </summary>
public static IAnsiConsole Console => _console.Value; public static IAnsiConsole Console => _console.Value;
@@ -30,8 +31,6 @@ namespace Spectre.Console
/// </summary> /// </summary>
public static Capabilities Capabilities => Console.Capabilities; public static Capabilities Capabilities => Console.Capabilities;
internal static bool Created { get; private set; }
/// <summary> /// <summary>
/// Gets the buffer width of the console. /// Gets the buffer width of the console.
/// </summary> /// </summary>
@@ -48,33 +47,6 @@ namespace Spectre.Console
get => Console.Height; get => Console.Height;
} }
/// <summary>
/// Gets or sets the foreground color.
/// </summary>
public static Color Foreground
{
get => Console.Foreground;
set => Console.SetColor(value, true);
}
/// <summary>
/// Gets or sets the background color.
/// </summary>
public static Color Background
{
get => Console.Background;
set => Console.SetColor(value, false);
}
/// <summary>
/// Gets or sets the text decoration.
/// </summary>
public static Decoration Decoration
{
get => Console.Decoration;
set => Console.Decoration = value;
}
/// <summary> /// <summary>
/// Creates a new <see cref="IAnsiConsole"/> instance /// Creates a new <see cref="IAnsiConsole"/> instance
/// from the provided settings. /// from the provided settings.
@@ -83,31 +55,7 @@ namespace Spectre.Console
/// <returns>An <see cref="IAnsiConsole"/> instance.</returns> /// <returns>An <see cref="IAnsiConsole"/> instance.</returns>
public static IAnsiConsole Create(AnsiConsoleSettings settings) public static IAnsiConsole Create(AnsiConsoleSettings settings)
{ {
return ConsoleBuilder.Build(settings); return AnsiConsoleBuilder.Build(settings);
}
/// <summary>
/// Resets colors and text decorations.
/// </summary>
public static void Reset()
{
Console.Reset();
}
/// <summary>
/// Resets the current applied text decorations.
/// </summary>
public static void ResetDecoration()
{
Console.ResetDecoration();
}
/// <summary>
/// Resets the current applied foreground and background colors.
/// </summary>
public static void ResetColors()
{
Console.ResetColors();
} }
} }
} }

View File

@@ -18,6 +18,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
public ColorSystemSupport ColorSystem { get; set; } public ColorSystemSupport ColorSystem { get; set; }
/// <summary>
/// Gets or sets the link identity generator.
/// </summary>
public ILinkIdentityGenerator? LinkIdentityGenerator { get; set; }
/// <summary> /// <summary>
/// Gets or sets the out buffer. /// Gets or sets the out buffer.
/// </summary> /// </summary>

View File

@@ -11,6 +11,17 @@ namespace Spectre.Console
/// </summary> /// </summary>
public bool SupportsAnsi { get; } public bool SupportsAnsi { get; }
/// <summary>
/// Gets a value indicating whether or not
/// the console support links.
/// </summary>
/// <remarks>
/// There is probably a lot of room for improvement here
/// once we have more information about the terminal
/// we're running inside.
/// </remarks>
public bool SupportLinks => SupportsAnsi && !LegacyConsole;
/// <summary> /// <summary>
/// Gets the color system. /// Gets the color system.
/// </summary> /// </summary>

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Generated 2020-08-03 15:17 // Generated 2020-09-18 10:42
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
using System.Text.RegularExpressions;
namespace Spectre.Console
{
/// <summary>
/// Utility for working with emojis.
/// </summary>
public static partial class Emoji
{
private static readonly Regex _emojiCode = new Regex(@"(:(\S*?):)", RegexOptions.Compiled);
/// <summary>
/// Replaces emoji markup with corresponding unicode characters.
/// </summary>
/// <param name="value">A string with emojis codes, e.g. "Hello :smiley:!".</param>
/// <returns>A string with emoji codes replaced with actual emoji.</returns>
public static string Replace(string value)
{
static string ReplaceEmoji(Match match) => _emojis[match.Groups[2].Value];
return _emojiCode.Replace(value, ReplaceEmoji);
}
}
}

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
@@ -28,30 +27,18 @@ namespace Spectre.Console
} }
var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole); var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole);
var segments = renderable.Render(options, console.Width).Where(x => !(x.Text.Length == 0 && !x.IsLineBreak)).ToArray();
segments = Segment.Merge(segments).ToArray();
using (console.PushStyle(Style.Plain)) var current = Style.Plain;
foreach (var segment in segments)
{ {
var segments = renderable.Render(options, console.Width).Where(x => !(x.Text.Length == 0 && !x.IsLineBreak)).ToArray(); if (string.IsNullOrEmpty(segment.Text))
segments = Segment.Merge(segments).ToArray();
var current = Style.Plain;
foreach (var segment in segments)
{ {
if (string.IsNullOrEmpty(segment.Text)) continue;
{
continue;
}
if (!segment.Style.Equals(current))
{
console.Foreground = segment.Style.Foreground;
console.Background = segment.Style.Background;
console.Decoration = segment.Style.Decoration;
current = segment.Style;
}
console.Write(segment.Text);
} }
console.Write(segment.Text, segment.Style);
} }
} }
} }

View File

@@ -1,339 +0,0 @@
using System;
using System.Globalization;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static partial class AnsiConsoleExtensions
{
/// <summary>
/// Writes the specified string value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, string value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (value != null)
{
console.Write(value);
}
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// signed integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, int value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// signed integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, int value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, uint value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, uint value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// signed integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, long value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// signed integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, long value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, ulong value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, ulong value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified single-precision
/// floating-point value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, float value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified single-precision
/// floating-point value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, float value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified double-precision
/// floating-point value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, double value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified double-precision
/// floating-point value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, double value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified decimal value, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, decimal value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified decimal value, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, decimal value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
Write(console, value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified boolean value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, bool value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified boolean value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, bool value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
Write(console, value.ToString(provider));
}
/// <summary>
/// Writes the specified Unicode character to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, char value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified Unicode character to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, char value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
Write(console, value.ToString(provider));
}
/// <summary>
/// Writes the specified array of Unicode characters to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, char[] value)
{
Write(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified array of Unicode characters to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, char[] value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
console.Write(value[index].ToString(provider));
}
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// to the console using the specified format information.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Write(this IAnsiConsole console, string format, params object[] args)
{
Write(console, CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// to the console using the specified format information.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Write(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
Write(console, string.Format(provider, format, args));
}
}
}

View File

@@ -1,368 +0,0 @@
using System;
using System.Globalization;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static partial class AnsiConsoleExtensions
{
/// <summary>
/// Writes an empty line to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
public static void WriteLine(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Write(Environment.NewLine);
}
/// <summary>
/// Writes the specified string value, followed by the
/// current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, string value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (value != null)
{
console.Write(value);
}
console.WriteLine();
}
/// <summary>
/// Writes the text representation of the specified 32-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, int value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, int value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified 32-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, uint value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, uint value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified 64-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, long value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, long value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified 64-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, ulong value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, ulong value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified single-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, float value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified single-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, float value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified double-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, double value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified double-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, double value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.WriteLine(value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified decimal value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, decimal value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified decimal value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, decimal value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
WriteLine(console, value.ToString(provider));
}
/// <summary>
/// Writes the text representation of the specified boolean value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, bool value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified boolean value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, bool value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
WriteLine(console, value.ToString(provider));
}
/// <summary>
/// Writes the specified Unicode character, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, char value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified Unicode character, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, char value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
WriteLine(console, value.ToString(provider));
}
/// <summary>
/// Writes the specified array of Unicode characters, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, char[] value)
{
WriteLine(console, CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified array of Unicode characters, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, char[] value)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
console.Write(value[index].ToString(provider));
}
console.WriteLine();
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// followed by the current line terminator, to the console
/// using the specified format information.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void WriteLine(this IAnsiConsole console, string format, params object[] args)
{
WriteLine(console, CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// followed by the current line terminator, to the console
/// using the specified format information.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void WriteLine(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
WriteLine(console, string.Format(provider, format, args));
}
}
}

View File

@@ -8,47 +8,34 @@ namespace Spectre.Console
public static partial class AnsiConsoleExtensions public static partial class AnsiConsoleExtensions
{ {
/// <summary> /// <summary>
/// Resets colors and text decorations. /// Writes an empty line to the console.
/// </summary> /// </summary>
/// <param name="console">The console to reset.</param> /// <param name="console">The console to write to.</param>
public static void Reset(this IAnsiConsole console) public static void WriteLine(this IAnsiConsole console)
{ {
if (console is null) if (console is null)
{ {
throw new ArgumentNullException(nameof(console)); throw new ArgumentNullException(nameof(console));
} }
console.ResetColors(); console.Write(Environment.NewLine, Style.Plain);
console.ResetDecoration();
} }
/// <summary> /// <summary>
/// Resets the current applied text decorations. /// Writes the specified string value, followed by the current line terminator, to the console.
/// </summary> /// </summary>
/// <param name="console">The console to reset the text decorations for.</param> /// <param name="console">The console to write to.</param>
public static void ResetDecoration(this IAnsiConsole console) /// <param name="text">The text to write.</param>
/// <param name="style">The text style.</param>
public static void WriteLine(this IAnsiConsole console, string text, Style style)
{ {
if (console is null) if (console is null)
{ {
throw new ArgumentNullException(nameof(console)); throw new ArgumentNullException(nameof(console));
} }
console.Decoration = Decoration.None; console.Write(text, style);
} console.WriteLine();
/// <summary>
/// Resets the current applied foreground and background colors.
/// </summary>
/// <param name="console">The console to reset colors for.</param>
public static void ResetColors(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Foreground = Color.Default;
console.Background = Color.Default;
} }
} }
} }

View File

@@ -12,9 +12,9 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam> /// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param> /// <param name="obj">The paddable object instance.</param>
/// <param name="padding">The left padding to apply.</param> /// <param name="left">The left padding.</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 PadLeft<T>(this T obj, int padding) public static T PadLeft<T>(this T obj, int left)
where T : class, IPaddable where T : class, IPaddable
{ {
if (obj is null) if (obj is null)
@@ -22,7 +22,25 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return SetPadding(obj, new Padding(padding, obj.Padding.Right)); return SetPadding(obj, new Padding(left, obj.Padding.Top, obj.Padding.Right, obj.Padding.Bottom));
}
/// <summary>
/// Sets the top padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="top">The top padding.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T PadTop<T>(this T obj, int top)
where T : class, IPaddable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetPadding(obj, new Padding(obj.Padding.Left, top, obj.Padding.Right, obj.Padding.Bottom));
} }
/// <summary> /// <summary>
@@ -30,9 +48,9 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam> /// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param> /// <param name="obj">The paddable object instance.</param>
/// <param name="padding">The right padding to apply.</param> /// <param name="right">The right padding.</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 PadRight<T>(this T obj, int padding) public static T PadRight<T>(this T obj, int right)
where T : class, IPaddable where T : class, IPaddable
{ {
if (obj is null) if (obj is null)
@@ -40,7 +58,25 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return SetPadding(obj, new Padding(obj.Padding.Left, padding)); return SetPadding(obj, new Padding(obj.Padding.Left, obj.Padding.Top, right, obj.Padding.Bottom));
}
/// <summary>
/// Sets the bottom padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="bottom">The bottom padding.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T PadBottom<T>(this T obj, int bottom)
where T : class, IPaddable
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
return SetPadding(obj, new Padding(obj.Padding.Left, obj.Padding.Top, obj.Padding.Right, bottom));
} }
/// <summary> /// <summary>
@@ -49,12 +85,14 @@ namespace Spectre.Console
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam> /// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param> /// <param name="obj">The paddable object instance.</param>
/// <param name="left">The left padding to apply.</param> /// <param name="left">The left padding to apply.</param>
/// <param name="top">The top padding to apply.</param>
/// <param name="right">The right padding to apply.</param> /// <param name="right">The right padding to apply.</param>
/// <param name="bottom">The bottom padding to apply.</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 SetPadding<T>(this T obj, int left, int right) public static T SetPadding<T>(this T obj, int left, int top, int right, int bottom)
where T : class, IPaddable where T : class, IPaddable
{ {
return SetPadding(obj, new Padding(left, right)); return SetPadding(obj, new Padding(left, top, right, bottom));
} }
/// <summary> /// <summary>

View File

@@ -66,5 +66,26 @@ namespace Spectre.Console
background: style.Background, background: style.Background,
decoration: decoration); decoration: decoration);
} }
/// <summary>
/// Creates a new style from the specified one with
/// the specified link.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="link">The link.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style WithLink(this Style style, string link)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
return new Style(
foreground: style.Foreground,
background: style.Background,
decoration: style.Decoration,
link: link);
}
} }
} }

View File

@@ -12,6 +12,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
Capabilities Capabilities { get; } Capabilities Capabilities { get; }
/// <summary>
/// Gets the console output encoding.
/// </summary>
Encoding Encoding { get; }
/// <summary> /// <summary>
/// Gets the buffer width of the console. /// Gets the buffer width of the console.
/// </summary> /// </summary>
@@ -22,30 +27,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
int Height { get; } int Height { get; }
/// <summary>
/// Gets the console output encoding.
/// </summary>
Encoding Encoding { get; }
/// <summary>
/// Gets or sets the current text decoration.
/// </summary>
Decoration Decoration { get; set; }
/// <summary>
/// Gets or sets the current foreground.
/// </summary>
Color Foreground { get; set; }
/// <summary>
/// Gets or sets the current background.
/// </summary>
Color Background { get; set; }
/// <summary> /// <summary>
/// Writes a string followed by a line terminator to the console. /// Writes a string followed by a line terminator to the console.
/// </summary> /// </summary>
/// <param name="text">The string to write.</param> /// <param name="text">The string to write.</param>
void Write(string text); /// <param name="style">The style to use.</param>
void Write(string text, Style style);
} }
} }

View File

@@ -0,0 +1,16 @@
namespace Spectre.Console
{
/// <summary>
/// Represents a link identity generator.
/// </summary>
public interface ILinkIdentityGenerator
{
/// <summary>
/// Generates an ID for the given link.
/// </summary>
/// <param name="link">The link.</param>
/// <param name="text">The link text.</param>
/// <returns>A unique ID for the link.</returns>
public int GenerateId(string link, string text);
}
}

View File

@@ -1,44 +1,74 @@
using System;
using System.Linq; using System.Linq;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {
internal static class AnsiBuilder internal sealed class AnsiBuilder
{ {
public static string GetAnsi( private readonly Capabilities _capabilities;
ColorSystem system, private readonly ILinkIdentityGenerator _linkHasher;
string text,
Decoration decoration, public AnsiBuilder(Capabilities capabilities, ILinkIdentityGenerator? linkHasher)
Color foreground,
Color background)
{ {
var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration); _capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
_linkHasher = linkHasher ?? new LinkIdentityGenerator();
}
public string GetAnsi(string text, Style style)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var codes = AnsiDecorationBuilder.GetAnsiCodes(style.Decoration);
// Got foreground? // Got foreground?
if (foreground != Color.Default) if (style.Foreground != Color.Default)
{ {
codes = codes.Concat(AnsiColorBuilder.GetAnsiCodes(system, foreground, foreground: true)); codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes(
_capabilities.ColorSystem,
style.Foreground,
true));
} }
// Got background? // Got background?
if (background != Color.Default) if (style.Background != Color.Default)
{ {
codes = codes.Concat(AnsiColorBuilder.GetAnsiCodes(system, background, foreground: false)); codes = codes.Concat(
AnsiColorBuilder.GetAnsiCodes(
_capabilities.ColorSystem,
style.Background,
false));
} }
var result = codes.ToArray(); var result = codes.ToArray();
if (result.Length == 0) if (result.Length == 0 && style.Link == null)
{ {
return text; return text;
} }
var lol = string.Concat( var ansiCodes = string.Join(";", result);
"\u001b[", var ansi = result.Length > 0
string.Join(";", result), ? $"\u001b[{ansiCodes}m{text}\u001b[0m"
"m", : text;
text,
"\u001b[0m");
return lol; if (style.Link != null && !_capabilities.LegacyConsole)
{
var link = style.Link;
// Empty links means we should take the URL from the text.
if (link.Equals(Constants.EmptyLink, StringComparison.Ordinal))
{
link = text;
}
var linkId = _linkHasher.GenerateId(link, text);
ansi = $"\u001b]8;id={linkId};{link}\u001b\\{ansi}\u001b]8;;\u001b\\";
}
return ansi;
} }
} }
} }

View File

@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {
internal static class ConsoleBuilder internal static class AnsiConsoleBuilder
{ {
public static IAnsiConsole Build(AnsiConsoleSettings settings) public static IAnsiConsole Build(AnsiConsoleSettings settings)
{ {
@@ -54,15 +54,18 @@ namespace Spectre.Console.Internal
? ColorSystemDetector.Detect(supportsAnsi) ? ColorSystemDetector.Detect(supportsAnsi)
: (ColorSystem)settings.ColorSystem; : (ColorSystem)settings.ColorSystem;
// Get the capabilities
var capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole);
// Create the renderer
if (supportsAnsi) if (supportsAnsi)
{ {
return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole) return new AnsiConsoleRenderer(buffer, capabilities, settings.LinkIdentityGenerator);
{ }
Decoration = Decoration.None, else
}; {
return new FallbackConsoleRenderer(buffer, capabilities);
} }
return new FallbackConsoleRenderer(buffer, colorSystem, legacyConsole);
} }
} }
} }

View File

@@ -7,13 +7,10 @@ namespace Spectre.Console.Internal
internal sealed class AnsiConsoleRenderer : IAnsiConsole internal sealed class AnsiConsoleRenderer : IAnsiConsole
{ {
private readonly TextWriter _out; private readonly TextWriter _out;
private readonly ColorSystem _system; private readonly AnsiBuilder _ansiBuilder;
public Capabilities Capabilities { get; } public Capabilities Capabilities { get; }
public Encoding Encoding { get; } public Encoding Encoding { get; }
public Decoration Decoration { get; set; }
public Color Foreground { get; set; }
public Color Background { get; set; }
public int Width public int Width
{ {
@@ -41,31 +38,31 @@ namespace Spectre.Console.Internal
} }
} }
public AnsiConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole) public AnsiConsoleRenderer(TextWriter @out, Capabilities capabilities, ILinkIdentityGenerator? linkHasher)
{ {
_out = @out ?? throw new ArgumentNullException(nameof(@out)); _out = @out ?? throw new ArgumentNullException(nameof(@out));
_system = system;
Capabilities = new Capabilities(true, system, legacyConsole); Capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8; Encoding = _out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
Foreground = Color.Default;
Background = Color.Default; _ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
Decoration = Decoration.None;
} }
public void Write(string text) public void Write(string text, Style style)
{ {
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
{ {
return; return;
} }
style ??= Style.Plain;
var parts = text.NormalizeLineEndings().Split(new[] { '\n' }); var parts = text.NormalizeLineEndings().Split(new[] { '\n' });
foreach (var (_, _, last, part) in parts.Enumerate()) foreach (var (_, _, last, part) in parts.Enumerate())
{ {
if (!string.IsNullOrEmpty(part)) if (!string.IsNullOrEmpty(part))
{ {
_out.Write(AnsiBuilder.GetAnsi(_system, part, Decoration, Foreground, Background)); _out.Write(_ansiBuilder.GetAnsi(part, style));
} }
if (!last) if (!last)

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Generated 2020-08-03 15:17 // Generated 2020-09-18 10:42
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.

View File

@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Generated 2020-08-03 15:17 // Generated 2020-09-18 10:42
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.

View File

@@ -4,5 +4,7 @@ namespace Spectre.Console.Internal
{ {
public const int DefaultBufferWidth = 80; public const int DefaultBufferWidth = 80;
public const int DefaultBufferHeight = 9001; public const int DefaultBufferHeight = 9001;
public const string EmptyLink = "https://emptylink";
} }
} }

View File

@@ -1,147 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class AnsiConsoleExtensions
{
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var current = new Style(console.Foreground, console.Background, console.Decoration);
console.SetColor(style.Foreground, true);
console.SetColor(style.Background, false);
console.Decoration = style.Decoration;
return new StyleScope(console, current);
}
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = foreground ? console.Foreground : console.Background;
console.SetColor(color, foreground);
return new ColorScope(console, current, foreground);
}
public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = console.Decoration;
console.Decoration = decoration;
return new DecorationScope(console, current);
}
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
if (foreground)
{
console.Foreground = color;
}
else
{
console.Background = color;
}
}
}
internal sealed class StyleScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Style _style;
public StyleScope(IAnsiConsole console, Style style)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_style = style ?? throw new ArgumentNullException(nameof(style));
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~StyleScope()
{
throw new InvalidOperationException("Style scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.SetColor(_style.Foreground, true);
_console.SetColor(_style.Background, false);
_console.Decoration = _style.Decoration;
}
}
internal sealed class ColorScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Color _color;
private readonly bool _foreground;
public ColorScope(IAnsiConsole console, Color color, bool foreground)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_color = color;
_foreground = foreground;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~ColorScope()
{
throw new InvalidOperationException("Color scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.SetColor(_color, _foreground);
}
}
internal sealed class DecorationScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Decoration _decoration;
public DecorationScope(IAnsiConsole console, Decoration decoration)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_decoration = decoration;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~DecorationScope()
{
throw new InvalidOperationException("Decoration scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.Decoration = _decoration;
}
}
}

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {
@@ -11,9 +12,9 @@ namespace Spectre.Console.Internal
private static readonly bool _alreadyNormalized private static readonly bool _alreadyNormalized
= Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase); = Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase);
public static int CellLength(this string text, Encoding encoding) public static int CellLength(this string text, RenderContext context)
{ {
return Cell.GetCellLength(encoding, text); return Cell.GetCellLength(context, text);
} }
public static string NormalizeLineEndings(this string text, bool native = false) public static string NormalizeLineEndings(this string text, bool native = false)
@@ -78,5 +79,28 @@ namespace Spectre.Console.Internal
return result.ToArray(); return result.ToArray();
} }
// https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/
public static int GetDeterministicHashCode(this string str)
{
unchecked
{
var hash1 = (5381 << 16) + 5381;
var hash2 = hash1;
for (var i = 0; i < str.Length; i += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1)
{
break;
}
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
return hash1 + (hash2 * 1566083941);
}
}
} }
} }

View File

@@ -6,16 +6,11 @@ namespace Spectre.Console.Internal
{ {
internal sealed class FallbackConsoleRenderer : IAnsiConsole internal sealed class FallbackConsoleRenderer : IAnsiConsole
{ {
private readonly ConsoleColor _defaultForeground;
private readonly ConsoleColor _defaultBackground;
private readonly TextWriter _out; private readonly TextWriter _out;
private readonly ColorSystem _system; private readonly ColorSystem _system;
private ConsoleColor _foreground; private Style? _lastStyle;
private ConsoleColor _background;
public Capabilities Capabilities { get; } public Capabilities Capabilities { get; }
public Encoding Encoding { get; } public Encoding Encoding { get; }
public int Width public int Width
@@ -44,73 +39,58 @@ namespace Spectre.Console.Internal
} }
} }
public Decoration Decoration { get; set; } public FallbackConsoleRenderer(TextWriter @out, Capabilities capabilities)
public Color Foreground
{ {
get => _foreground; if (capabilities == null)
set
{ {
_foreground = Color.ToConsoleColor(value); throw new ArgumentNullException(nameof(capabilities));
if (_system != ColorSystem.NoColors && _out.IsStandardOut())
{
if ((int)_foreground == -1)
{
_foreground = _defaultForeground;
}
System.Console.ForegroundColor = _foreground;
}
} }
}
public Color Background _out = @out ?? throw new ArgumentNullException(nameof(@out));
{ _system = capabilities.ColorSystem;
get => _background;
set
{
_background = Color.ToConsoleColor(value);
if (_system != ColorSystem.NoColors && _out.IsStandardOut())
{
if ((int)_background == -1)
{
_background = _defaultBackground;
}
if (_system != ColorSystem.NoColors)
{
System.Console.BackgroundColor = _background;
}
}
}
}
public FallbackConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole)
{
_out = @out;
_system = system;
if (_out.IsStandardOut()) if (_out.IsStandardOut())
{ {
_defaultForeground = System.Console.ForegroundColor;
_defaultBackground = System.Console.BackgroundColor;
Encoding = System.Console.OutputEncoding; Encoding = System.Console.OutputEncoding;
} }
else else
{ {
_defaultForeground = ConsoleColor.Gray;
_defaultBackground = ConsoleColor.Black;
Encoding = Encoding.UTF8; Encoding = Encoding.UTF8;
} }
Capabilities = new Capabilities(false, _system, legacyConsole); Capabilities = capabilities;
} }
public void Write(string text) public void Write(string text, Style style)
{ {
if (_lastStyle?.Equals(style) != true)
{
SetStyle(style);
}
_out.Write(text.NormalizeLineEndings(native: true)); _out.Write(text.NormalizeLineEndings(native: true));
} }
private void SetStyle(Style style)
{
_lastStyle = style;
if (_out.IsStandardOut())
{
System.Console.ResetColor();
var background = Color.ToConsoleColor(style.Background);
if (_system != ColorSystem.NoColors && _out.IsStandardOut() && (int)background != -1)
{
System.Console.BackgroundColor = background;
}
var foreground = Color.ToConsoleColor(style.Foreground);
if (_system != ColorSystem.NoColors && _out.IsStandardOut() && (int)foreground != -1)
{
System.Console.ForegroundColor = foreground;
}
}
}
} }
} }

View File

@@ -0,0 +1,31 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class LinkIdentityGenerator : ILinkIdentityGenerator
{
private readonly Random _random;
public LinkIdentityGenerator()
{
_random = new Random(DateTime.Now.Millisecond);
}
public int GenerateId(string link, string text)
{
if (link is null)
{
throw new ArgumentNullException(nameof(link));
}
link += text ?? string.Empty;
unchecked
{
return Math.Abs(
link.GetHashCode() +
_random.Next(0, int.MaxValue));
}
}
}
}

View File

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

View File

@@ -8,6 +8,11 @@ namespace Spectre.Console.Internal
{ {
public static Paragraph Parse(string text, Style? style = null) public static Paragraph Parse(string text, Style? style = null)
{ {
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
style ??= Style.Plain; style ??= Style.Plain;
var result = new Paragraph(); var result = new Paragraph();
@@ -41,7 +46,7 @@ namespace Spectre.Console.Internal
{ {
// Get the effecive style. // Get the effecive style.
var effectiveStyle = style.Combine(stack.Reverse()); var effectiveStyle = style.Combine(stack.Reverse());
result.Append(token.Value, effectiveStyle); result.Append(Emoji.Replace(token.Value), effectiveStyle);
} }
else else
{ {

View File

@@ -35,6 +35,7 @@ namespace Spectre.Console.Internal
var effectiveDecoration = (Decoration?)null; var effectiveDecoration = (Decoration?)null;
var effectiveForeground = (Color?)null; var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)null; var effectiveBackground = (Color?)null;
var effectiveLink = (string?)null;
var parts = text.Split(new[] { ' ' }); var parts = text.Split(new[] { ' ' });
var foreground = true; var foreground = true;
@@ -51,6 +52,23 @@ namespace Spectre.Console.Internal
continue; continue;
} }
if (part.StartsWith("link=", StringComparison.OrdinalIgnoreCase))
{
if (effectiveLink != null)
{
error = "A link has already been set.";
return null;
}
effectiveLink = part.Substring(5);
continue;
}
else if (part.StartsWith("link", StringComparison.OrdinalIgnoreCase))
{
effectiveLink = Constants.EmptyLink;
continue;
}
var decoration = DecorationTable.GetDecoration(part); var decoration = DecorationTable.GetDecoration(part);
if (decoration != null) if (decoration != null)
{ {
@@ -116,7 +134,11 @@ namespace Spectre.Console.Internal
} }
error = null; error = null;
return new Style(effectiveForeground, effectiveBackground, effectiveDecoration); return new Style(
effectiveForeground,
effectiveBackground,
effectiveDecoration,
effectiveLink);
} }
[SuppressMessage("Design", "CA1031:Do not catch general exception types")] [SuppressMessage("Design", "CA1031:Do not catch general exception types")]

View File

@@ -3,7 +3,7 @@ using System;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
/// Represents a measurement. /// Represents padding.
/// </summary> /// </summary>
public struct Padding : IEquatable<Padding> public struct Padding : IEquatable<Padding>
{ {
@@ -12,20 +12,53 @@ namespace Spectre.Console
/// </summary> /// </summary>
public int Left { get; } public int Left { get; }
/// <summary>
/// Gets the top padding.
/// </summary>
public int Top { get; }
/// <summary> /// <summary>
/// Gets the right padding. /// Gets the right padding.
/// </summary> /// </summary>
public int Right { get; } public int Right { get; }
/// <summary>
/// Gets the bottom padding.
/// </summary>
public int Bottom { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Padding"/> struct.
/// </summary>
/// <param name="size">The padding for all sides.</param>
public Padding(int size)
: this(size, size, size, size)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Padding"/> struct.
/// </summary>
/// <param name="horizontal">The left and right padding.</param>
/// <param name="vertical">The top and bottom padding.</param>
public Padding(int horizontal, int vertical)
: this(horizontal, vertical, horizontal, vertical)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Padding"/> struct. /// Initializes a new instance of the <see cref="Padding"/> struct.
/// </summary> /// </summary>
/// <param name="left">The left padding.</param> /// <param name="left">The left padding.</param>
/// <param name="top">The top padding.</param>
/// <param name="right">The right padding.</param> /// <param name="right">The right padding.</param>
public Padding(int left, int right) /// <param name="bottom">The bottom padding.</param>
public Padding(int left, int top, int right, int bottom)
{ {
Left = left; Left = left;
Top = top;
Right = right; Right = right;
Bottom = bottom;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -41,7 +74,9 @@ namespace Spectre.Console
{ {
var hash = (int)2166136261; var hash = (int)2166136261;
hash = (hash * 16777619) ^ Left.GetHashCode(); hash = (hash * 16777619) ^ Left.GetHashCode();
hash = (hash * 16777619) ^ Top.GetHashCode();
hash = (hash * 16777619) ^ Right.GetHashCode(); hash = (hash * 16777619) ^ Right.GetHashCode();
hash = (hash * 16777619) ^ Bottom.GetHashCode();
return hash; return hash;
} }
} }
@@ -49,7 +84,10 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Padding other) public bool Equals(Padding other)
{ {
return Left == other.Left && Right == other.Right; return Left == other.Left
&& Top == other.Top
&& Right == other.Right
&& Bottom == other.Bottom;
} }
/// <summary> /// <summary>
@@ -75,12 +113,21 @@ namespace Spectre.Console
} }
/// <summary> /// <summary>
/// Gets the horizontal padding. /// Gets the padding width.
/// </summary> /// </summary>
/// <returns>The horizontal padding.</returns> /// <returns>The padding width.</returns>
public int GetHorizontalPadding() public int GetWidth()
{ {
return Left + Right; return Left + Right;
} }
/// <summary>
/// Gets the padding height.
/// </summary>
/// <returns>The padding height.</returns>
public int GetHeight()
{
return Top + Bottom;
}
} }
} }

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text;
using Spectre.Console.Internal; using Spectre.Console.Internal;
namespace Spectre.Console.Rendering namespace Spectre.Console.Rendering
@@ -82,11 +81,11 @@ namespace Spectre.Console.Rendering
/// Gets the number of cells that this segment /// Gets the number of cells that this segment
/// occupies in the console. /// occupies in the console.
/// </summary> /// </summary>
/// <param name="encoding">The encoding to use.</param> /// <param name="context">The render context.</param>
/// <returns>The number of cells that this segment occupies in the console.</returns> /// <returns>The number of cells that this segment occupies in the console.</returns>
public int CellLength(Encoding encoding) public int CellLength(RenderContext context)
{ {
return Text.CellLength(encoding); return Text.CellLength(context);
} }
/// <summary> /// <summary>
@@ -266,17 +265,17 @@ namespace Spectre.Console.Rendering
/// </summary> /// </summary>
/// <param name="segment">The segment to split.</param> /// <param name="segment">The segment to split.</param>
/// <param name="overflow">The overflow strategy to use.</param> /// <param name="overflow">The overflow strategy to use.</param>
/// <param name="encoding">The encoding to use.</param> /// <param name="context">The render context.</param>
/// <param name="width">The maximum width.</param> /// <param name="width">The maximum width.</param>
/// <returns>A list of segments that has been split.</returns> /// <returns>A list of segments that has been split.</returns>
public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, Encoding encoding, int width) public static List<Segment> SplitOverflow(Segment segment, Overflow? overflow, RenderContext context, int width)
{ {
if (segment is null) if (segment is null)
{ {
throw new ArgumentNullException(nameof(segment)); throw new ArgumentNullException(nameof(segment));
} }
if (segment.CellLength(encoding) <= width) if (segment.CellLength(context) <= width)
{ {
return new List<Segment>(1) { segment }; return new List<Segment>(1) { segment };
} }
@@ -288,7 +287,7 @@ namespace Spectre.Console.Rendering
if (overflow == Overflow.Fold) if (overflow == Overflow.Fold)
{ {
var totalLength = segment.Text.CellLength(encoding); var totalLength = segment.Text.CellLength(context);
var lengthLeft = totalLength; var lengthLeft = totalLength;
while (lengthLeft > 0) while (lengthLeft > 0)
{ {
@@ -311,12 +310,12 @@ namespace Spectre.Console.Rendering
return result; return result;
} }
internal static Segment TruncateWithEllipsis(string text, Style style, Encoding encoding, int maxWidth) internal static Segment TruncateWithEllipsis(string text, Style style, RenderContext context, int maxWidth)
{ {
return SplitOverflow( return SplitOverflow(
new Segment(text, style), new Segment(text, style),
Overflow.Ellipsis, Overflow.Ellipsis,
encoding, context,
maxWidth)[0]; maxWidth)[0];
} }

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text;
namespace Spectre.Console.Rendering namespace Spectre.Console.Rendering
{ {
@@ -19,11 +18,11 @@ namespace Spectre.Console.Rendering
/// <summary> /// <summary>
/// Gets the cell width of the segment line. /// Gets the cell width of the segment line.
/// </summary> /// </summary>
/// <param name="encoding">The encoding to use.</param> /// <param name="context">The render context.</param>
/// <returns>The cell width of the segment line.</returns> /// <returns>The cell width of the segment line.</returns>
public int CellWidth(Encoding encoding) public int CellWidth(RenderContext context)
{ {
return this.Sum(line => line.CellLength(encoding)); return this.Sum(line => line.CellLength(context));
} }
/// <summary> /// <summary>

View File

@@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" /> <AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" /> <None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
</ItemGroup> </ItemGroup>
@@ -20,6 +20,9 @@
<Compile Update="Border.*.cs"> <Compile Update="Border.*.cs">
<DependentUpon>Border.cs</DependentUpon> <DependentUpon>Border.cs</DependentUpon>
</Compile> </Compile>
<Compile Update="Emoji.*.cs">
<DependentUpon>Emoji.cs</DependentUpon>
</Compile>
<Compile Update="Extensions/AnsiConsoleExtensions.*.cs"> <Compile Update="Extensions/AnsiConsoleExtensions.*.cs">
<DependentUpon>Extensions/AnsiConsoleExtensions.cs</DependentUpon> <DependentUpon>Extensions/AnsiConsoleExtensions.cs</DependentUpon>
</Compile> </Compile>
@@ -32,6 +35,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Wcwidth" Version="0.2.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -41,12 +45,6 @@
<Compile Update="Extensions\AnsiConsoleExtensions.Rendering.cs"> <Compile Update="Extensions\AnsiConsoleExtensions.Rendering.cs">
<DependentUpon>AnsiConsoleExtensions.cs</DependentUpon> <DependentUpon>AnsiConsoleExtensions.cs</DependentUpon>
</Compile> </Compile>
<Compile Update="Extensions\AnsiConsoleExtensions.Write.cs">
<DependentUpon>AnsiConsoleExtensions.cs</DependentUpon>
</Compile>
<Compile Update="Extensions\AnsiConsoleExtensions.WriteLine.cs">
<DependentUpon>AnsiConsoleExtensions.cs</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion> <AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion>

View File

@@ -24,6 +24,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
public Decoration Decoration { get; } public Decoration Decoration { get; }
/// <summary>
/// Gets the link.
/// </summary>
public string? Link { get; }
/// <summary> /// <summary>
/// Gets an <see cref="Style"/> with the /// Gets an <see cref="Style"/> with the
/// default colors and without text decoration. /// default colors and without text decoration.
@@ -31,7 +36,7 @@ namespace Spectre.Console
public static Style Plain { get; } = new Style(); public static Style Plain { get; } = new Style();
private Style() private Style()
: this(null, null, null) : this(null, null, null, null)
{ {
} }
@@ -41,11 +46,13 @@ namespace Spectre.Console
/// <param name="foreground">The foreground color.</param> /// <param name="foreground">The foreground color.</param>
/// <param name="background">The background color.</param> /// <param name="background">The background color.</param>
/// <param name="decoration">The text decoration.</param> /// <param name="decoration">The text decoration.</param>
public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null) /// <param name="link">The link.</param>
public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null, string? link = null)
{ {
Foreground = foreground ?? Color.Default; Foreground = foreground ?? Color.Default;
Background = background ?? Color.Default; Background = background ?? Color.Default;
Decoration = decoration ?? Decoration.None; Decoration = decoration ?? Decoration.None;
Link = link;
} }
/// <summary> /// <summary>
@@ -78,13 +85,23 @@ namespace Spectre.Console
return new Style(decoration: decoration); return new Style(decoration: decoration);
} }
/// <summary>
/// Creates a new style from the specified link.
/// </summary>
/// <param name="link">The link.</param>
/// <returns>A new <see cref="Style"/> with the specified link.</returns>
public static Style WithLink(string link)
{
return new Style(link: link);
}
/// <summary> /// <summary>
/// Creates a copy of the current <see cref="Style"/>. /// Creates a copy of the current <see cref="Style"/>.
/// </summary> /// </summary>
/// <returns>A copy of the current <see cref="Style"/>.</returns> /// <returns>A copy of the current <see cref="Style"/>.</returns>
public Style Clone() public Style Clone()
{ {
return new Style(Foreground, Background, Decoration); return new Style(Foreground, Background, Decoration, Link);
} }
/// <summary> /// <summary>
@@ -111,7 +128,13 @@ namespace Spectre.Console
background = other.Background; background = other.Background;
} }
return new Style(foreground, background, Decoration | other.Decoration); var link = Link;
if (!string.IsNullOrWhiteSpace(other.Link))
{
link = other.Link;
}
return new Style(foreground, background, Decoration | other.Decoration, link);
} }
/// <summary> /// <summary>
@@ -158,6 +181,12 @@ namespace Spectre.Console
hash = (hash * 16777619) ^ Foreground.GetHashCode(); hash = (hash * 16777619) ^ Foreground.GetHashCode();
hash = (hash * 16777619) ^ Background.GetHashCode(); hash = (hash * 16777619) ^ Background.GetHashCode();
hash = (hash * 16777619) ^ Decoration.GetHashCode(); hash = (hash * 16777619) ^ Decoration.GetHashCode();
if (Link != null)
{
hash = (hash * 16777619) ^ Link?.GetHashCode() ?? 0;
}
return hash; return hash;
} }
} }
@@ -178,7 +207,8 @@ namespace Spectre.Console
return Foreground.Equals(other.Foreground) && return Foreground.Equals(other.Foreground) &&
Background.Equals(other.Background) && Background.Equals(other.Background) &&
Decoration == other.Decoration; Decoration == other.Decoration &&
string.Equals(Link, other.Link, StringComparison.Ordinal);
} }
} }
} }

View File

@@ -13,7 +13,7 @@ namespace Spectre.Console
private readonly List<IRenderable> _items; private readonly List<IRenderable> _items;
/// <inheritdoc/> /// <inheritdoc/>
public Padding Padding { get; set; } = new Padding(0, 1); public Padding Padding { get; set; } = new Padding(0, 0, 1, 0);
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not the columns should /// Gets or sets a value indicating whether or not the columns should

View File

@@ -21,6 +21,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding of the column. /// Gets or sets the padding of the column.
/// Vertical padding (top and bottom) is ignored.
/// </summary> /// </summary>
public Padding Padding public Padding Padding
{ {
@@ -48,7 +49,7 @@ namespace Spectre.Console
/// </summary> /// </summary>
public GridColumn() public GridColumn()
{ {
_padding = new Padding(0, 2); _padding = new Padding(0, 0, 2, 0);
} }
} }
} }

View File

@@ -0,0 +1,106 @@
using System.Collections.Generic;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console
{
/// <summary>
/// Represents padding around a <see cref="IRenderable"/> object.
/// </summary>
public sealed class Padder : Renderable, IPaddable, IExpandable
{
private readonly IRenderable _child;
/// <inheritdoc/>
public Padding Padding { get; set; } = new Padding(1, 1, 1, 1);
/// <summary>
/// Gets or sets a value indicating whether or not the padding should
/// fit the available space. If <c>false</c>, the padding width will be
/// auto calculated. Defaults to <c>false</c>.
/// </summary>
public bool Expand { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Padder"/> class.
/// </summary>
/// <param name="child">The thing to pad.</param>
/// <param name="padding">The padding. Defaults to <c>1,1,1,1</c> if null.</param>
public Padder(IRenderable child, Padding? padding = null)
{
_child = child;
Padding = padding ?? Padding;
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
var paddingWidth = Padding.GetWidth();
var measurement = _child.Measure(context, maxWidth - paddingWidth);
return new Measurement(
measurement.Min + paddingWidth,
measurement.Max + paddingWidth);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var paddingWidth = Padding.GetWidth();
var childWidth = maxWidth - paddingWidth;
if (!Expand)
{
var measurement = _child.Measure(context, maxWidth - paddingWidth);
childWidth = measurement.Max;
}
var width = childWidth + paddingWidth;
var result = new List<Segment>();
// Top padding
for (var i = 0; i < Padding.Top; i++)
{
result.Add(new Segment(new string(' ', width)));
result.Add(Segment.LineBreak);
}
var child = _child.Render(context, maxWidth - paddingWidth);
foreach (var (_, _, _, line) in Segment.SplitLines(child).Enumerate())
{
// Left padding
if (Padding.Left != 0)
{
result.Add(new Segment(new string(' ', Padding.Left)));
}
result.AddRange(line);
// Right padding
if (Padding.Right != 0)
{
result.Add(new Segment(new string(' ', Padding.Right)));
}
// Missing space on right side?
var lineWidth = line.CellWidth(context);
var diff = width - lineWidth - Padding.Left - Padding.Right;
if (diff > 0)
{
result.Add(new Segment(new string(' ', diff)));
}
result.Add(Segment.LineBreak);
}
// Bottom padding
for (var i = 0; i < Padding.Bottom; i++)
{
result.Add(new Segment(new string(' ', width)));
result.Add(Segment.LineBreak);
}
return result;
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding. /// Gets or sets the padding.
/// </summary> /// </summary>
public Padding Padding { get; set; } = new Padding(1, 1); public Padding Padding { get; set; } = new Padding(1, 0, 1, 0);
/// <summary> /// <summary>
/// Gets or sets the header. /// Gets or sets the header.
@@ -61,10 +61,11 @@ namespace Spectre.Console
/// <inheritdoc/> /// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth) protected override Measurement Measure(RenderContext context, int maxWidth)
{ {
var childWidth = _child.Measure(context, maxWidth); var child = new Padder(_child, Padding);
var childWidth = ((IRenderable)child).Measure(context, maxWidth);
return new Measurement( return new Measurement(
childWidth.Min + EdgeWidth + Padding.GetHorizontalPadding(), childWidth.Min + EdgeWidth,
childWidth.Max + EdgeWidth + Padding.GetHorizontalPadding()); childWidth.Max + EdgeWidth);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -73,16 +74,16 @@ namespace Spectre.Console
var border = Border.GetSafeBorder((context.LegacyConsole || !context.Unicode) && UseSafeBorder); var border = Border.GetSafeBorder((context.LegacyConsole || !context.Unicode) && UseSafeBorder);
var borderStyle = BorderStyle ?? Style.Plain; var borderStyle = BorderStyle ?? Style.Plain;
var paddingWidth = Padding.GetHorizontalPadding(); var child = new Padder(_child, Padding);
var childWidth = maxWidth - EdgeWidth - paddingWidth; var childWidth = maxWidth - EdgeWidth;
if (!Expand) if (!Expand)
{ {
var measurement = _child.Measure(context, maxWidth - EdgeWidth - paddingWidth); var measurement = ((IRenderable)child).Measure(context, maxWidth - EdgeWidth);
childWidth = measurement.Max; childWidth = measurement.Max;
} }
var panelWidth = childWidth + EdgeWidth + paddingWidth; var panelWidth = childWidth + EdgeWidth;
panelWidth = Math.Min(panelWidth, maxWidth); panelWidth = Math.Min(panelWidth, maxWidth);
var result = new List<Segment>(); var result = new List<Segment>();
@@ -91,22 +92,16 @@ namespace Spectre.Console
AddTopBorder(result, context, border, borderStyle, panelWidth); AddTopBorder(result, context, border, borderStyle, panelWidth);
// Split the child segments into lines. // Split the child segments into lines.
var childSegments = _child.Render(context, childWidth); var childSegments = ((IRenderable)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));
// Left padding
if (Padding.Left > 0)
{
result.Add(new Segment(new string(' ', Padding.Left)));
}
var content = new List<Segment>(); var content = new List<Segment>();
content.AddRange(line); content.AddRange(line);
// Do we need to pad the panel? // Do we need to pad the panel?
var length = line.Sum(segment => segment.CellLength(context.Encoding)); var length = line.Sum(segment => segment.CellLength(context));
if (length < childWidth) if (length < childWidth)
{ {
var diff = childWidth - length; var diff = childWidth - length;
@@ -115,12 +110,6 @@ namespace Spectre.Console
result.AddRange(content); result.AddRange(content);
// Right padding
if (Padding.Right > 0)
{
result.Add(new Segment(new string(' ', Padding.Right)));
}
result.Add(new Segment(border.GetPart(BorderPart.CellRight), borderStyle)); result.Add(new Segment(border.GetPart(BorderPart.CellRight), borderStyle));
result.Add(Segment.LineBreak); result.Add(Segment.LineBreak);
} }
@@ -149,9 +138,9 @@ namespace Spectre.Console
var rightSpacing = 0; var rightSpacing = 0;
var headerWidth = panelWidth - (EdgeWidth * 2); var headerWidth = panelWidth - (EdgeWidth * 2);
var header = Segment.TruncateWithEllipsis(Header.Text, Header.Style ?? borderStyle, context.Encoding, headerWidth); var header = Segment.TruncateWithEllipsis(Header.Text, Header.Style ?? borderStyle, context, headerWidth);
var excessWidth = headerWidth - header.CellLength(context.Encoding); var excessWidth = headerWidth - header.CellLength(context);
if (excessWidth > 0) if (excessWidth > 0)
{ {
switch (Header.Alignment ?? Justify.Left) switch (Header.Alignment ?? Justify.Left)

View File

@@ -119,8 +119,8 @@ namespace Spectre.Console
return new Measurement(0, 0); return new Measurement(0, 0);
} }
var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding))); var min = _lines.Max(line => line.Max(segment => segment.CellLength(context)));
var max = _lines.Max(x => x.CellWidth(context.Encoding)); var max = _lines.Max(x => x.CellWidth(context));
return new Measurement(min, Math.Min(max, maxWidth)); return new Measurement(min, Math.Min(max, maxWidth));
} }
@@ -144,7 +144,7 @@ namespace Spectre.Console
var justification = context.Justification ?? Alignment ?? Justify.Left; var justification = context.Justification ?? Alignment ?? Justify.Left;
foreach (var (_, _, last, line) in lines.Enumerate()) foreach (var (_, _, last, line) in lines.Enumerate())
{ {
var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding)); var length = line.Sum(l => l.StripLineEndings().CellLength(context));
if (length < maxWidth) if (length < maxWidth)
{ {
// Justify right side // Justify right side
@@ -193,7 +193,7 @@ namespace Spectre.Console
private List<SegmentLine> SplitLines(RenderContext context, int maxWidth) private List<SegmentLine> SplitLines(RenderContext context, int maxWidth)
{ {
if (_lines.Max(x => x.CellWidth(context.Encoding)) <= maxWidth) if (_lines.Max(x => x.CellWidth(context)) <= maxWidth)
{ {
return Clone(); return Clone();
} }
@@ -244,15 +244,15 @@ namespace Spectre.Console
continue; continue;
} }
var length = current.CellLength(context.Encoding); var length = current.CellLength(context);
if (length > maxWidth) if (length > maxWidth)
{ {
// The current segment is longer than the width of the console, // The current segment is longer than the width of the console,
// so we will need to crop it up, into new segments. // so we will need to crop it up, into new segments.
var segments = Segment.SplitOverflow(current, Overflow, context.Encoding, maxWidth); var segments = Segment.SplitOverflow(current, Overflow, context, maxWidth);
if (segments.Count > 0) if (segments.Count > 0)
{ {
if (line.CellWidth(context.Encoding) + segments[0].CellLength(context.Encoding) > maxWidth) if (line.CellWidth(context) + segments[0].CellLength(context) > maxWidth)
{ {
lines.Add(line); lines.Add(line);
line = new SegmentLine(); line = new SegmentLine();
@@ -272,7 +272,7 @@ namespace Spectre.Console
} }
else else
{ {
if (line.CellWidth(context.Encoding) + length > maxWidth) if (line.CellWidth(context) + length > maxWidth)
{ {
line.Add(Segment.Empty); line.Add(Segment.Empty);
lines.Add(line); lines.Add(line);

View File

@@ -221,12 +221,12 @@ namespace Spectre.Console
result.Add(Segment.LineBreak); result.Add(Segment.LineBreak);
} }
// Make cells the same shape
cells = Segment.MakeSameHeight(cellHeight, cells);
// Iterate through each cell row // Iterate through each cell row
foreach (var cellRowIndex in Enumerable.Range(0, cellHeight)) foreach (var cellRowIndex in Enumerable.Range(0, cellHeight))
{ {
// Make cells the same shape
cells = Segment.MakeSameHeight(cellHeight, cells);
foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate()) foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate())
{ {
if (firstCell && showBorder) if (firstCell && showBorder)
@@ -250,7 +250,7 @@ namespace Spectre.Console
result.AddRange(cell[cellRowIndex]); result.AddRange(cell[cellRowIndex]);
// Pad cell content right // Pad cell content right
var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding)); var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context));
if (length < columnWidths[cellIndex]) if (length < columnWidths[cellIndex])
{ {
result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length))); result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));
@@ -403,7 +403,7 @@ namespace Spectre.Console
private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth) private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth)
{ {
var padding = column.Padding.GetHorizontalPadding(); var padding = column.Padding.GetWidth();
// Predetermined width? // Predetermined width?
if (column.Width != null) if (column.Width != null)
@@ -438,7 +438,7 @@ namespace Spectre.Console
var hideBorder = !Border.Visible; var hideBorder = !Border.Visible;
var separators = hideBorder ? 0 : _columns.Count - 1; var separators = hideBorder ? 0 : _columns.Count - 1;
var edges = hideBorder ? 0 : EdgeCount; var edges = hideBorder ? 0 : EdgeCount;
var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0; var padding = includePadding ? _columns.Select(x => x.Padding.GetWidth()).Sum() : 0;
if (!PadRightCell) if (!PadRightCell)
{ {

View File

@@ -21,6 +21,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding of the column. /// Gets or sets the padding of the column.
/// Vertical padding (top and bottom) is ignored.
/// </summary> /// </summary>
public Padding Padding { get; set; } public Padding Padding { get; set; }
@@ -52,7 +53,7 @@ namespace Spectre.Console
{ {
Text = renderable ?? throw new ArgumentNullException(nameof(renderable)); Text = renderable ?? throw new ArgumentNullException(nameof(renderable));
Width = null; Width = null;
Padding = new Padding(1, 1); Padding = new Padding(1, 0, 1, 0);
NoWrap = false; NoWrap = false;
Alignment = null; Alignment = null;
} }