mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a0380ae0a | ||
|
|
ae92c606bb | ||
|
|
68e92f3365 | ||
|
|
39a8588dc3 | ||
|
|
3c3afe7439 | ||
|
|
971f9032ba | ||
|
|
5149557560 | ||
|
|
e0947708c9 | ||
|
|
b1db8a9403 | ||
|
|
a2f8652575 | ||
|
|
672faa131f |
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@@ -70,6 +70,7 @@ jobs:
|
||||
dotnet example panels
|
||||
dotnet example colors
|
||||
dotnet example emojis
|
||||
dotnet example exceptions
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@
|
||||
[Pp]ackages/
|
||||
/.artifacts/
|
||||
/[Tt]ools/
|
||||
.idea
|
||||
.DS_Store
|
||||
|
||||
# Cakeup
|
||||
|
||||
@@ -1,8 +1,50 @@
|
||||
# Documentation
|
||||
|
||||
Preview the documentation locally by running the following
|
||||
from your favourite shell:
|
||||
To start contributing to the [Spectre.Console](https://github.com/spectresystems/spectre.console) documentation, you will need the [.NET Core SDK](https://dot.net) 3.1 or higher.
|
||||
|
||||
## Running Preview Site
|
||||
|
||||
The documentation site uses [Statiq](https://statiq.dev), a static site generator. To build the documentation site run the following in a command-line terminal.
|
||||
|
||||
```
|
||||
> dotnet run -- preview --virtual-dir "spectre.console"
|
||||
> dotnet run preview --virtual-dir "spectre.console"
|
||||
```
|
||||
|
||||
After the build is complete, you can navigate to [http://localhost:5080/spectre.consle](http://localhost:5080/spectre.console).
|
||||
|
||||
**Note that the site runs under a virtual directory.**
|
||||
|
||||
## Editing Content
|
||||
|
||||
The documentation is written using [Markdown](https://www.markdownguide.org/basic-syntax/).
|
||||
|
||||
Markdown files can be found under the following directories:
|
||||
|
||||
- [/input](./input)
|
||||
- [/appendix](./input/appendix)
|
||||
|
||||
## Editing Layout
|
||||
|
||||
Layout and styling can also be found in the [input](./input) directory. Look for Sass, Css, and Images under the [assets](./input/assets) directory.
|
||||
|
||||
## Custom Build Features
|
||||
|
||||
The documentation site has custom enhancements to Statiq located under the [./src](./src) directory. Enhancements to the build process include:
|
||||
|
||||
- [Extension Methods](./src/Extensions)
|
||||
- [Models](./src/Models)
|
||||
- [Pipelines](./src/Pipelines)
|
||||
- [Shortcodes](./src/Shortcodes)
|
||||
- [Utilities](./src/Utilities)
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Spectre Systems AB
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -5,8 +5,8 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="/spectre.console/assets/bootstrap/bootstrap.css" rel="stylesheet" />
|
||||
<link href="/spectre.console/assets/css/styles.css" rel="stylesheet" />
|
||||
<link href="@Context.GetLink("/assets/bootstrap/bootstrap.css")" rel="stylesheet" />
|
||||
<link href="@Context.GetLink("/assets/css/styles.css")" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&family=Roboto+Slab:wght@400;700&family=Roboto:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet" data-no-mirror>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.19.0/themes/prism.css">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<nav id="topnav" class="navbar navbar-expand-lg navbar-light">
|
||||
<div class="container py-3">
|
||||
<a class="navbar-brand" href="/spectre.console"><img id="logo" src="/spectre.console/assets/logo.svg" alt="Spectre.Console"> Spectre.Console</a>
|
||||
<a class="navbar-brand" href="@Context.GetLink("/")"><img id="logo" src="@Context.GetLink("/assets/logo.svg")" alt="Spectre.Console"> Spectre.Console</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
@@ -13,4 +13,19 @@ in markup text such as `AnsiConsole.Markup("[maroon on blue]Hello[/]")`.
|
||||
|
||||
# Standard colors
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
class="form-control w-100 filter"
|
||||
data-table="color-results"
|
||||
type="text" placeholder="Search Colors..." autocomplete="off"
|
||||
aria-label="Search Colors">
|
||||
</div>
|
||||
|
||||
<?# ColorTable /?>
|
||||
|
||||
<script type="text/javascript" src="../assets/js/table-search.js"></script>
|
||||
@@ -30,9 +30,43 @@ var phrase = "Mmmm :birthday_cake:";
|
||||
var rendered = Emoji.Replace(phrase);
|
||||
```
|
||||
|
||||
# Remapping or adding an emoji
|
||||
|
||||
Sometimes you want to remap an existing emoji, or
|
||||
add a completely new one. For this you can use the
|
||||
`Emoji.Remap` method. This approach works both with
|
||||
markup strings and `Emoji.Replace`.
|
||||
|
||||
```csharp
|
||||
// Remap the emoji
|
||||
Emoji.Remap("globe_showing_europe_africa", "😄");
|
||||
|
||||
// Render markup
|
||||
AnsiConsole.MarkupLine("Hello :globe_showing_europe_africa:!");
|
||||
|
||||
// Replace emojis in string
|
||||
var phrase = "Hello :globe_showing_europe_africa:!";
|
||||
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._
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
class="form-control w-100 filter"
|
||||
data-table="emoji-results"
|
||||
type="text" placeholder="Search Emojis..." autocomplete="off"
|
||||
aria-label="Search Emojis">
|
||||
</div>
|
||||
|
||||
<?# EmojiTable /?>
|
||||
|
||||
<script type="text/javascript" src="../assets/js/table-search.js"></script>
|
||||
12
docs/input/appendix/index.cshtml
Normal file
12
docs/input/appendix/index.cshtml
Normal file
@@ -0,0 +1,12 @@
|
||||
Title: Appendix
|
||||
Order: 10
|
||||
---
|
||||
|
||||
<h1>Sections</h1>
|
||||
|
||||
<ul>
|
||||
@foreach (IDocument child in OutputPages.GetChildrenOf(Document))
|
||||
{
|
||||
<li>@Html.DocumentLink(child)</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -1,10 +0,0 @@
|
||||
Title: Appendix
|
||||
Order: 10
|
||||
---
|
||||
|
||||
# Sections
|
||||
|
||||
* [Styles](xref:styles)
|
||||
* [Colors](xref:colors)
|
||||
* [Borders](xref:borders)
|
||||
* [Emojis](xref:emojis)
|
||||
BIN
docs/input/assets/images/compact_exception.png
Normal file
BIN
docs/input/assets/images/compact_exception.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
BIN
docs/input/assets/images/custom_exception.png
Normal file
BIN
docs/input/assets/images/custom_exception.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
BIN
docs/input/assets/images/exception.png
Normal file
BIN
docs/input/assets/images/exception.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 421 KiB |
35
docs/input/assets/js/table-search.js
Normal file
35
docs/input/assets/js/table-search.js
Normal file
@@ -0,0 +1,35 @@
|
||||
$(document).ready(function () {
|
||||
|
||||
$('.filter').each(function () {
|
||||
let input = this;
|
||||
let table = document.getElementById(input.dataset.table);
|
||||
|
||||
input.onkeyup = function (event) {
|
||||
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
let value = input.value.toUpperCase();
|
||||
let rows = table.getElementsByClassName('search-row');
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
|
||||
let match =
|
||||
new RegExp(value, "i").test(row.textContent) ||
|
||||
value === '';
|
||||
|
||||
if (match) {
|
||||
row.style.display = 'table-row';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}; // keyup
|
||||
})
|
||||
|
||||
|
||||
}); // ready
|
||||
52
docs/input/exceptions.md
Normal file
52
docs/input/exceptions.md
Normal file
@@ -0,0 +1,52 @@
|
||||
Title: Exceptions
|
||||
Order: 3
|
||||
---
|
||||
|
||||
Exceptions isn't always readable when viewed in the terminal.
|
||||
You can make exception a bit more readable by using the `WriteException` method.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.WriteException(ex);
|
||||
```
|
||||
|
||||
<img src="assets/images/exception.png" style="max-width: 100%;">
|
||||
|
||||
## Shortening parts
|
||||
|
||||
You can also shorten specific parts of the exception to make it even
|
||||
more readable, and make paths clickable hyperlinks. Whether or not
|
||||
the hyperlinks are clickable is up to the terminal.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.WriteException(ex,
|
||||
ExceptionFormat.ShortenPaths | ExceptionFormat.ShortenTypes |
|
||||
ExceptionFormat.ShortenMethods | ExceptionFormat.ShowLinks);
|
||||
```
|
||||
|
||||
<img src="assets/images/compact_exception.png" style="max-width: 100%;">
|
||||
|
||||
## Customizing exception output
|
||||
|
||||
In addition to shorten specific part of the exception, you can
|
||||
also override the default styling.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.WriteException(ex, new ExceptionSettings
|
||||
{
|
||||
Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks,
|
||||
Style = new ExceptionStyle
|
||||
{
|
||||
Exception = Style.WithForeground(Color.Grey),
|
||||
Message = Style.WithForeground(Color.White),
|
||||
NonEmphasized = Style.WithForeground(Color.Cornsilk1),
|
||||
Parenthesis = Style.WithForeground(Color.Cornsilk1),
|
||||
Method = Style.WithForeground(Color.Red),
|
||||
ParameterName = Style.WithForeground(Color.Cornsilk1),
|
||||
ParameterType = Style.WithForeground(Color.Red),
|
||||
Path = Style.WithForeground(Color.Red),
|
||||
LineNumber = Style.WithForeground(Color.Cornsilk1),
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<img src="assets/images/custom_exception.png" style="max-width: 100%;">
|
||||
@@ -43,6 +43,12 @@ AnsiConsole.Markup("[[Hello]] "); // [Hello]
|
||||
AnsiConsole.Markup("[red][[World]][/]"); // [World]
|
||||
```
|
||||
|
||||
You can also use the `SafeMarkup` extension method.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".SafeMarkup());
|
||||
```
|
||||
|
||||
# Setting background color
|
||||
|
||||
You can set the background color in markup by prefixing the color with
|
||||
@@ -65,6 +71,15 @@ For a list of emoji, see the [Emojis](xref:styles) appendix section.
|
||||
|
||||
# Colors
|
||||
|
||||
In the examples above, all colors was referenced by their name,
|
||||
but you can also use the hex or rgb representation for colors in markdown.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]Foo[/] ");
|
||||
AnsiConsole.Markup("[#ff0000]Bar[/] ");
|
||||
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
|
||||
```
|
||||
|
||||
For a list of colors, see the [Colors](xref:colors) appendix section.
|
||||
|
||||
# Styles
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace Docs
|
||||
|
||||
public static Bootstrapper ConfigureDeployment(this Bootstrapper bootstrapper, string deployBranch)
|
||||
{
|
||||
if (bootstrapper != null)
|
||||
{
|
||||
bootstrapper.AddSetting(Constants.Deployment.TargetBranch, deployBranch);
|
||||
}
|
||||
bootstrapper?.AddSetting(Constants.Deployment.TargetBranch, deployBranch);
|
||||
return bootstrapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,13 @@ namespace Docs.Pipelines
|
||||
{
|
||||
public class ColorsPipeline : Pipeline
|
||||
{
|
||||
public const string Url = "https://raw.githubusercontent.com/spectresystems/spectre.console/main/resources/scripts/Generator/Data/colors.json";
|
||||
|
||||
public ColorsPipeline()
|
||||
{
|
||||
InputModules = new ModuleList
|
||||
{
|
||||
new ExecuteConfig(
|
||||
Config.FromContext(ctx => {
|
||||
return new ReadWeb(Url);
|
||||
return new ReadWeb(Constants.Colors.Url);
|
||||
}))
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Docs.Shortcodes
|
||||
.First().Object;
|
||||
|
||||
// Headers
|
||||
var table = new XElement("table", new XAttribute("class", "table"));
|
||||
var table = new XElement("table", new XAttribute("class", "table"), new XAttribute("id", "color-results"));
|
||||
var header = new XElement("tr", new XAttribute("class", "color-row"));
|
||||
header.Add(new XElement("th", ""));
|
||||
header.Add(new XElement("th", "#"));
|
||||
@@ -44,7 +44,7 @@ namespace Docs.Shortcodes
|
||||
var clr = new XElement("td", new XElement("code", color.ClrName));
|
||||
|
||||
// Create row
|
||||
var row = new XElement("tr");
|
||||
var row = new XElement("tr", new XAttribute("class", "search-row"));
|
||||
row.Add(rep);
|
||||
row.Add(name);
|
||||
row.Add(number);
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace Docs.Shortcodes
|
||||
.First().Object;
|
||||
|
||||
// Headers
|
||||
var table = new XElement("table", new XAttribute("class", "table"));
|
||||
var header = new XElement("tr", new XAttribute("class", "emoji-row"));
|
||||
var table = new XElement("table", new XAttribute("class", "table"), new XAttribute("id", "emoji-results"));
|
||||
var header = new XElement("tr", new XAttribute("class", "emoji-row-header"));
|
||||
header.Add(new XElement("th", ""));
|
||||
header.Add(new XElement("th", "Markup"));
|
||||
header.Add(new XElement("th", "Constant"));
|
||||
@@ -28,9 +28,9 @@ namespace Docs.Shortcodes
|
||||
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 icon = $"&#x{emoji.Code.Replace("U+", string.Empty)};";
|
||||
|
||||
var row = new XElement("tr");
|
||||
var row = new XElement("tr", new XAttribute("class", "search-row"));
|
||||
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)));
|
||||
|
||||
@@ -6,11 +6,19 @@ namespace EmojiExample
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
// Markup
|
||||
// Show a known emoji
|
||||
RenderEmoji();
|
||||
|
||||
// Show a remapped emoji
|
||||
Emoji.Remap("globe_showing_europe_africa", Emoji.Known.GrinningFaceWithSmilingEyes);
|
||||
RenderEmoji();
|
||||
}
|
||||
|
||||
private static void RenderEmoji()
|
||||
{
|
||||
AnsiConsole.Render(
|
||||
new Panel("[yellow]Hello :globe_showing_europe_africa:![/]")
|
||||
.RoundedBorder()
|
||||
.SetHeader("Markup"));
|
||||
.RoundedBorder());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
examples/Exceptions/Exceptions.csproj
Normal file
15
examples/Exceptions/Exceptions.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Title>Exceptions</Title>
|
||||
<Description>Demonstrates how to render formatted exceptions.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
66
examples/Exceptions/Program.cs
Normal file
66
examples/Exceptions/Program.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Security.Authentication;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Exceptions
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
DoMagic(42, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Render(new Panel("[u]Default[/]").Expand());
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.WriteException(ex);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Render(new Panel("[u]Compact[/]").Expand());
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.Render(new Panel("[u]Custom colors[/]").Expand());
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.WriteException(ex, new ExceptionSettings
|
||||
{
|
||||
Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks,
|
||||
Style = new ExceptionStyle
|
||||
{
|
||||
Exception = Style.WithForeground(Color.Grey),
|
||||
Message = Style.WithForeground(Color.White),
|
||||
NonEmphasized = Style.WithForeground(Color.Cornsilk1),
|
||||
Parenthesis = Style.WithForeground(Color.Cornsilk1),
|
||||
Method = Style.WithForeground(Color.Red),
|
||||
ParameterName = Style.WithForeground(Color.Cornsilk1),
|
||||
ParameterType = Style.WithForeground(Color.Red),
|
||||
Path = Style.WithForeground(Color.Red),
|
||||
LineNumber = Style.WithForeground(Color.Cornsilk1),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoMagic(int foo, string[,] bar)
|
||||
{
|
||||
try
|
||||
{
|
||||
CheckCredentials(foo, bar);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Whaaat?", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckCredentials(int qux, string[,] corgi)
|
||||
{
|
||||
throw new InvalidCredentialException("The credentials are invalid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,3 +87,6 @@ dotnet_diagnostic.RCS1227.severity = none
|
||||
|
||||
# IDE0004: Remove Unnecessary Cast
|
||||
dotnet_diagnostic.IDE0004.severity = warning
|
||||
|
||||
# CA1810: Initialize reference type static fields inline
|
||||
dotnet_diagnostic.CA1810.severity = none
|
||||
@@ -24,3 +24,6 @@ dotnet_diagnostic.CA2000.severity = none
|
||||
|
||||
# SA1118: Parameter should not span multiple lines
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
|
||||
# CA1031: Do not catch general exception types
|
||||
dotnet_diagnostic.CA1031.severity = none
|
||||
|
||||
23
src/Spectre.Console.Tests/Data/Exceptions.cs
Normal file
23
src/Spectre.Console.Tests/Data/Exceptions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Tests.Data
|
||||
{
|
||||
public static class TestExceptions
|
||||
{
|
||||
[SuppressMessage("Usage", "CA1801:Review unused parameters", Justification = "<Pending>")]
|
||||
public static bool MethodThatThrows(int? number) => throw new InvalidOperationException("Throwing!");
|
||||
|
||||
public static void ThrowWithInnerException()
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodThatThrows(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException("Something threw!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,34 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Spectre.Console.Tests
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
private static readonly Regex _lineNumberRegex = new Regex(":\\d+", RegexOptions.Singleline);
|
||||
private static readonly Regex _filenameRegex = new Regex("\\sin\\s.*cs:nn", RegexOptions.Multiline);
|
||||
|
||||
public static string NormalizeLineEndings(this string text)
|
||||
{
|
||||
return text?.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase)
|
||||
?.Replace("\r", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static string NormalizeStackTrace(this string text)
|
||||
{
|
||||
text = _lineNumberRegex.Replace(text, match =>
|
||||
{
|
||||
return ":nn";
|
||||
});
|
||||
|
||||
return _filenameRegex.Replace(text, match =>
|
||||
{
|
||||
var value = match.Value;
|
||||
var index = value.LastIndexOfAny(new[] { '\\', '/' });
|
||||
var filename = value.Substring(index + 1, value.Length - index - 1);
|
||||
|
||||
return $" in /xyz/{filename}";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Spectre.Console.Tests/Tools/MarkupConsoleFixture.cs
Normal file
44
src/Spectre.Console.Tests/Tools/MarkupConsoleFixture.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Tests.Tools
|
||||
{
|
||||
public sealed class MarkupConsoleFixture : IDisposable, IAnsiConsole
|
||||
{
|
||||
private readonly StringWriter _writer;
|
||||
private readonly IAnsiConsole _console;
|
||||
|
||||
public string Output => _writer.ToString().TrimEnd('\n');
|
||||
|
||||
public Capabilities Capabilities => _console.Capabilities;
|
||||
public Encoding Encoding => _console.Encoding;
|
||||
public int Width { get; }
|
||||
public int Height => _console.Height;
|
||||
|
||||
public MarkupConsoleFixture(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(Segment segment)
|
||||
{
|
||||
_console.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
@@ -50,5 +51,16 @@ namespace Spectre.Console.Tests
|
||||
|
||||
Writer.Write(segment.Text);
|
||||
}
|
||||
|
||||
public string[] WriteExceptionAndGetLines(Exception ex, ExceptionFormats formats = ExceptionFormats.Default)
|
||||
{
|
||||
this.WriteException(ex, formats);
|
||||
|
||||
return Output.NormalizeStackTrace()
|
||||
.NormalizeLineEndings()
|
||||
.Split(new char[] { '\n' })
|
||||
.Select(line => line.TrimEnd())
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,39 @@ namespace Spectre.Console.Tests.Unit
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheToMarkupMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Default_Color()
|
||||
{
|
||||
// Given, When
|
||||
var result = Color.Default.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("default");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Known_Color()
|
||||
{
|
||||
// Given, When
|
||||
var result = Color.Red.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("red");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Custom_Color()
|
||||
{
|
||||
// Given, When
|
||||
var result = new Color(255, 1, 12).ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("#FF010C");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheToStringMethod
|
||||
{
|
||||
[Fact]
|
||||
|
||||
99
src/Spectre.Console.Tests/Unit/ExceptionTests.cs
Normal file
99
src/Spectre.Console.Tests/Unit/ExceptionTests.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Tests.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public sealed class ExceptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Write_Exception()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(4);
|
||||
result[0].ShouldBe("System.InvalidOperationException: Throwing!");
|
||||
result[1].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[2].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception>b__0_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[3].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Exception_With_Shortened_Types()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex, ExceptionFormats.ShortenTypes);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(4);
|
||||
result[0].ShouldBe("InvalidOperationException: Throwing!");
|
||||
result[1].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[2].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception_With_Shortened_Types>b__1_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[3].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Exception_With_Shortened_Methods()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.MethodThatThrows(null));
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex, ExceptionFormats.ShortenMethods);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(4);
|
||||
result[0].ShouldBe("System.InvalidOperationException: Throwing!");
|
||||
result[1].ShouldBe(" at MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[2].ShouldBe(" at <Should_Write_Exception_With_Shortened_Methods>b__2_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[3].ShouldBe(" at GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Write_Exception_With_Inner_Exception()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 1024);
|
||||
var dex = GetException(() => TestExceptions.ThrowWithInnerException());
|
||||
|
||||
// When
|
||||
var result = console.WriteExceptionAndGetLines(dex);
|
||||
|
||||
// Then
|
||||
result.Length.ShouldBe(7);
|
||||
result[0].ShouldBe("System.InvalidOperationException: Something threw!");
|
||||
result[1].ShouldBe(" System.InvalidOperationException: Throwing!");
|
||||
result[2].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(Nullable`1 number) in /xyz/Exceptions.cs:nn");
|
||||
result[3].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.ThrowWithInnerException() in /xyz/Exceptions.cs:nn");
|
||||
result[4].ShouldBe(" at Spectre.Console.Tests.Data.TestExceptions.ThrowWithInnerException() in /xyz/Exceptions.cs:nn");
|
||||
result[5].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception_With_Inner_Exception>b__3_0() in /xyz/ExceptionTests.cs:nn");
|
||||
result[6].ShouldBe(" at Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn");
|
||||
}
|
||||
|
||||
public static Exception GetException(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return e;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Exception harness failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Shouldly;
|
||||
using Spectre.Console.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
@@ -298,5 +300,33 @@ namespace Spectre.Console.Tests.Unit
|
||||
console.Lines[3].ShouldBe("│ └─────────────┘ │");
|
||||
console.Lines[4].ShouldBe("└─────────────────┘");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Wrap_Content_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new PlainConsole(width: 84);
|
||||
var rows = new List<IRenderable>();
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn().PadLeft(2).PadRight(0));
|
||||
grid.AddColumn(new GridColumn().PadLeft(1).PadRight(0));
|
||||
grid.AddRow("at", "[grey]System.Runtime.CompilerServices.TaskAwaiter.[/][yellow]HandleNonSuccessAndDebuggerNotification[/]([blue]Task[/] task)");
|
||||
rows.Add(grid);
|
||||
|
||||
var panel = new Panel(grid)
|
||||
.Expand().RoundedBorder()
|
||||
.SetBorderStyle(Style.WithForeground(Color.Grey))
|
||||
.SetHeader("Short paths ", Style.WithForeground(Color.Grey));
|
||||
|
||||
// When
|
||||
console.Render(panel);
|
||||
|
||||
// Then
|
||||
console.Lines.Count.ShouldBe(4);
|
||||
console.Lines[0].ShouldBe("╭─Short paths ─────────────────────────────────────────────────────────────────────╮");
|
||||
console.Lines[1].ShouldBe("│ at System.Runtime.CompilerServices.TaskAwaiter. │");
|
||||
console.Lines[2].ShouldBe("│ HandleNonSuccessAndDebuggerNotification(Task task) │");
|
||||
console.Lines[3].ShouldBe("╰──────────────────────────────────────────────────────────────────────────────────╯");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,5 +317,60 @@ namespace Spectre.Console.Tests.Unit
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheToMarkupMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Style_With_Foreground_Color()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(Color.Red);
|
||||
|
||||
// When
|
||||
var result = style.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("red");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Style_With_Foreground_And_Background_Color()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(Color.Red, Color.Green);
|
||||
|
||||
// When
|
||||
var result = style.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("red on green");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Style_With_Foreground_And_Background_Color_And_Decoration()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(Color.Red, Color.Green, Decoration.Bold | Decoration.Underline);
|
||||
|
||||
// When
|
||||
var result = style.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("bold underline red on green");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Style_With_Only_Background_Color()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(background: Color.Green);
|
||||
|
||||
// When
|
||||
var result = style.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("default on green");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Links", "..\examples\Links\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emojis", "..\examples\Emojis\Emojis.csproj", "{1EABB956-957F-4C1A-8AC0-FD19C8F3C2F2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptions", "..\examples\Exceptions\Exceptions.csproj", "{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\.github\workflows\ci.yaml = ..\.github\workflows\ci.yaml
|
||||
..\.github\workflows\docs.yaml = ..\.github\workflows\docs.yaml
|
||||
..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -177,6 +186,18 @@ Global
|
||||
{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
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x64.Build.0 = Release|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -191,6 +212,8 @@ Global
|
||||
{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}
|
||||
{90C081A7-7C1D-4A4A-82B6-8FF473C3EA32} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
|
||||
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||
|
||||
30
src/Spectre.Console/AnsiConsole.Exceptions.cs
Normal file
30
src/Spectre.Console/AnsiConsole.Exceptions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// A console capable of writing ANSI escape sequences.
|
||||
/// </summary>
|
||||
public static partial class AnsiConsole
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes an exception to the console.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to write to the console.</param>
|
||||
/// <param name="format">The exception format options.</param>
|
||||
public static void WriteException(Exception exception, ExceptionFormats format = ExceptionFormats.Default)
|
||||
{
|
||||
Console.WriteException(exception, format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an exception to the console.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to write to the console.</param>
|
||||
/// <param name="settings">The exception settings.</param>
|
||||
public static void WriteException(Exception exception, ExceptionSettings settings)
|
||||
{
|
||||
Console.WriteException(exception, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
@@ -9,9 +5,6 @@ namespace Spectre.Console
|
||||
/// </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; }
|
||||
|
||||
@@ -42,20 +35,6 @@ namespace Spectre.Console
|
||||
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>
|
||||
@@ -78,8 +57,7 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public static void ResetColors()
|
||||
{
|
||||
Foreground = _defaultForeground;
|
||||
Background = _defaultBackground;
|
||||
CurrentStyle = Style.Plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Spectre.Console
|
||||
ColorSystem = ColorSystemSupport.Detect,
|
||||
Out = System.Console.Out,
|
||||
});
|
||||
Initialize(System.Console.Out);
|
||||
Created = true;
|
||||
return console;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
@@ -11,70 +7,16 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public abstract partial class BoxBorder
|
||||
{
|
||||
private readonly Dictionary<BoxBorderPart, string> _lookup;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the safe border for this border or <c>null</c> if none exist.
|
||||
/// </summary>
|
||||
public virtual BoxBorder? SafeBorder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BoxBorder"/> class.
|
||||
/// </summary>
|
||||
protected BoxBorder()
|
||||
{
|
||||
_lookup = Initialize();
|
||||
}
|
||||
|
||||
private Dictionary<BoxBorderPart, string> Initialize()
|
||||
{
|
||||
var lookup = new Dictionary<BoxBorderPart, string>();
|
||||
foreach (BoxBorderPart? part in Enum.GetValues(typeof(BoxBorderPart)))
|
||||
{
|
||||
if (part == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = GetBorderPart(part.Value);
|
||||
if (text.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
||||
}
|
||||
|
||||
lookup.Add(part.Value, GetBorderPart(part.Value));
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <param name="count">The number of repetitions.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(BoxBorderPart part, int count)
|
||||
{
|
||||
// TODO: This need some optimization...
|
||||
return string.Join(string.Empty, Enumerable.Repeat(GetBorderPart(part)[0], count));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return _lookup[part].ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character representing the specified border part.
|
||||
/// Gets the string representation of the specified border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get the character representation for.</param>
|
||||
/// <returns>A character representation of the specified border part.</returns>
|
||||
protected abstract string GetBorderPart(BoxBorderPart part);
|
||||
public abstract string GetPart(BoxBorderPart part);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,9 +245,37 @@ namespace Spectre.Console
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the color to a markup string.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="string"/> representing the color as markup.</returns>
|
||||
public string ToMarkup()
|
||||
{
|
||||
if (IsDefault)
|
||||
{
|
||||
return "default";
|
||||
}
|
||||
|
||||
if (Number != null)
|
||||
{
|
||||
var name = ColorTable.GetName(Number.Value);
|
||||
if (!string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}", R, G, B);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
if (IsDefault)
|
||||
{
|
||||
return "default";
|
||||
}
|
||||
|
||||
if (Number != null)
|
||||
{
|
||||
var name = ColorTable.GetName(Number.Value);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Spectre.Console
|
||||
@@ -8,6 +10,35 @@ namespace Spectre.Console
|
||||
public static partial class Emoji
|
||||
{
|
||||
private static readonly Regex _emojiCode = new Regex(@"(:(\S*?):)", RegexOptions.Compiled);
|
||||
private static readonly Dictionary<string, string> _remappings;
|
||||
|
||||
static Emoji()
|
||||
{
|
||||
_remappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remaps a specific emoji tag with a new emoji.
|
||||
/// </summary>
|
||||
/// <param name="tag">The emoji tag.</param>
|
||||
/// <param name="emoji">The emoji.</param>
|
||||
public static void Remap(string tag, string emoji)
|
||||
{
|
||||
if (tag is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tag));
|
||||
}
|
||||
|
||||
if (emoji is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(emoji));
|
||||
}
|
||||
|
||||
tag = tag.TrimStart(':').TrimEnd(':');
|
||||
emoji = emoji.TrimStart(':').TrimEnd(':');
|
||||
|
||||
_remappings[tag] = emoji;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces emoji markup with corresponding unicode characters.
|
||||
@@ -16,7 +47,23 @@ namespace Spectre.Console
|
||||
/// <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];
|
||||
static string ReplaceEmoji(Match match)
|
||||
{
|
||||
var key = match.Groups[2].Value;
|
||||
|
||||
if (_remappings.Count > 0 && _remappings.TryGetValue(key, out var remappedEmoji))
|
||||
{
|
||||
return remappedEmoji;
|
||||
}
|
||||
|
||||
if (_emojis.TryGetValue(key, out var emoji))
|
||||
{
|
||||
return emoji;
|
||||
}
|
||||
|
||||
return match.Value;
|
||||
}
|
||||
|
||||
return _emojiCode.Replace(value, ReplaceEmoji);
|
||||
}
|
||||
}
|
||||
|
||||
41
src/Spectre.Console/ExceptionFormat.cs
Normal file
41
src/Spectre.Console/ExceptionFormat.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents how an exception is formatted.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ExceptionFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// The default formatting.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not paths should be shortened.
|
||||
/// </summary>
|
||||
ShortenPaths = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not types should be shortened.
|
||||
/// </summary>
|
||||
ShortenTypes = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not methods should be shortened.
|
||||
/// </summary>
|
||||
ShortenMethods = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to show paths as links in the terminal.
|
||||
/// </summary>
|
||||
ShowLinks = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Shortens everything that can be shortened.
|
||||
/// </summary>
|
||||
ShortenEverything = ShortenMethods | ShortenTypes | ShortenPaths,
|
||||
}
|
||||
}
|
||||
27
src/Spectre.Console/ExceptionSettings.cs
Normal file
27
src/Spectre.Console/ExceptionSettings.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception settings.
|
||||
/// </summary>
|
||||
public sealed class ExceptionSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the exception format.
|
||||
/// </summary>
|
||||
public ExceptionFormats Format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception style.
|
||||
/// </summary>
|
||||
public ExceptionStyle Style { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExceptionSettings"/> class.
|
||||
/// </summary>
|
||||
public ExceptionSettings()
|
||||
{
|
||||
Format = ExceptionFormats.Default;
|
||||
Style = new ExceptionStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/Spectre.Console/ExceptionStyle.cs
Normal file
58
src/Spectre.Console/ExceptionStyle.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent an exception style.
|
||||
/// </summary>
|
||||
public sealed class ExceptionStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the message color.
|
||||
/// </summary>
|
||||
public Style Message { get; set; } = new Style(Color.Red, Color.Default, Decoration.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception color.
|
||||
/// </summary>
|
||||
public Style Exception { get; set; } = new Style(Color.White);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method color.
|
||||
/// </summary>
|
||||
public Style Method { get; set; } = new Style(Color.Yellow);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter type color.
|
||||
/// </summary>
|
||||
public Style ParameterType { get; set; } = new Style(Color.Blue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter name color.
|
||||
/// </summary>
|
||||
public Style ParameterName { get; set; } = new Style(Color.Silver);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parenthesis color.
|
||||
/// </summary>
|
||||
public Style Parenthesis { get; set; } = new Style(Color.Silver);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path color.
|
||||
/// </summary>
|
||||
public Style Path { get; set; } = new Style(Color.Yellow, Color.Default, Decoration.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the line number color.
|
||||
/// </summary>
|
||||
public Style LineNumber { get; set; } = new Style(Color.Blue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color for dimmed text such as "at" or "in".
|
||||
/// </summary>
|
||||
public Style Dimmed { get; set; } = new Style(Color.Grey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color for non emphasized items.
|
||||
/// </summary>
|
||||
public Style NonEmphasized { get; set; } = new Style(Color.Silver);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="IAnsiConsole"/>.
|
||||
/// </summary>
|
||||
public static partial class AnsiConsoleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes an exception to the console.
|
||||
/// </summary>
|
||||
/// <param name="console">The console.</param>
|
||||
/// <param name="exception">The exception to write to the console.</param>
|
||||
/// <param name="format">The exception format options.</param>
|
||||
public static void WriteException(this IAnsiConsole console, Exception exception, ExceptionFormats format = ExceptionFormats.Default)
|
||||
{
|
||||
Render(console, exception.GetRenderable(format));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an exception to the console.
|
||||
/// </summary>
|
||||
/// <param name="console">The console.</param>
|
||||
/// <param name="exception">The exception to write to the console.</param>
|
||||
/// <param name="settings">The exception settings.</param>
|
||||
public static void WriteException(this IAnsiConsole console, Exception exception, ExceptionSettings settings)
|
||||
{
|
||||
Render(console, exception.GetRenderable(settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,9 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
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();
|
||||
var segments = renderable.Render(options, console.Width).ToArray();
|
||||
segments = Segment.Merge(segments).ToArray();
|
||||
|
||||
var current = Style.Plain;
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (string.IsNullOrEmpty(segment.Text))
|
||||
|
||||
51
src/Spectre.Console/Extensions/ExceptionExtensions.cs
Normal file
51
src/Spectre.Console/Extensions/ExceptionExtensions.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="Exception"/>.
|
||||
/// </summary>
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IRenderable"/> representation of the exception.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to format.</param>
|
||||
/// <param name="format">The exception format options.</param>
|
||||
/// <returns>A <see cref="IRenderable"/> representing the exception.</returns>
|
||||
public static IRenderable GetRenderable(this Exception exception, ExceptionFormats format = ExceptionFormats.Default)
|
||||
{
|
||||
if (exception is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
return GetRenderable(exception, new ExceptionSettings
|
||||
{
|
||||
Format = format,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IRenderable"/> representation of the exception.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception to format.</param>
|
||||
/// <param name="settings">The exception settings.</param>
|
||||
/// <returns>A <see cref="IRenderable"/> representing the exception.</returns>
|
||||
public static IRenderable GetRenderable(this Exception exception, ExceptionSettings settings)
|
||||
{
|
||||
if (exception is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
return ExceptionFormatter.Format(exception, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Spectre.Console/Extensions/StringExtensions.cs
Normal file
26
src/Spectre.Console/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="string"/>.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the string to something that is safe to
|
||||
/// use in a markup string.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to convert.</param>
|
||||
/// <returns>A string that is safe to use in a markup string.</returns>
|
||||
public static string SafeMarkup(this string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return text
|
||||
.Replace("[", "[[")
|
||||
.Replace("]", "]]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class DecorationTable
|
||||
{
|
||||
private static readonly Dictionary<string, Decoration?> _lookup;
|
||||
private static readonly Dictionary<Decoration, string> _reverseLookup;
|
||||
|
||||
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
|
||||
static DecorationTable()
|
||||
@@ -28,6 +30,21 @@ namespace Spectre.Console.Internal
|
||||
{ "strikethrough", Decoration.Strikethrough },
|
||||
{ "s", Decoration.Strikethrough },
|
||||
};
|
||||
|
||||
_reverseLookup = new Dictionary<Decoration, string>();
|
||||
foreach (var (name, decoration) in _lookup)
|
||||
{
|
||||
// Cannot happen, but the compiler thinks so...
|
||||
if (decoration == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_reverseLookup.ContainsKey(decoration.Value))
|
||||
{
|
||||
_reverseLookup[decoration.Value] = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Decoration? GetDecoration(string name)
|
||||
@@ -35,5 +52,23 @@ namespace Spectre.Console.Internal
|
||||
_lookup.TryGetValue(name, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> GetMarkupNames(Decoration decoration)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
||||
Enum.GetValues(typeof(Decoration))
|
||||
.Cast<Decoration>()
|
||||
.Where(flag => (decoration & flag) != 0)
|
||||
.ForEach(flag =>
|
||||
{
|
||||
if (flag != Decoration.None && _reverseLookup.TryGetValue(flag, out var name))
|
||||
{
|
||||
result.Add(name);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
165
src/Spectre.Console/Internal/ExceptionFormatter.cs
Normal file
165
src/Spectre.Console/Internal/ExceptionFormatter.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Internal;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class ExceptionFormatter
|
||||
{
|
||||
public static IRenderable Format(Exception exception, ExceptionSettings settings)
|
||||
{
|
||||
if (exception is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
var info = ExceptionParser.Parse(exception.ToString());
|
||||
if (info == null)
|
||||
{
|
||||
return new Text(exception.ToString());
|
||||
}
|
||||
|
||||
return GetException(info, settings);
|
||||
}
|
||||
|
||||
private static IRenderable GetException(ExceptionInfo info, ExceptionSettings settings)
|
||||
{
|
||||
if (info is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
}
|
||||
|
||||
return new Rows(new IRenderable[]
|
||||
{
|
||||
GetMessage(info, settings),
|
||||
GetStackFrames(info, settings),
|
||||
}).Expand();
|
||||
}
|
||||
|
||||
private static Markup GetMessage(ExceptionInfo ex, ExceptionSettings settings)
|
||||
{
|
||||
var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0;
|
||||
var type = Emphasize(ex.Type, new[] { '.' }, settings.Style.Exception, shortenTypes, settings);
|
||||
var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.SafeMarkup()}[/]";
|
||||
return new Markup(string.Concat(type, ": ", message));
|
||||
}
|
||||
|
||||
private static Grid GetStackFrames(ExceptionInfo ex, ExceptionSettings settings)
|
||||
{
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn().PadLeft(2).PadRight(0).NoWrap());
|
||||
grid.AddColumn(new GridColumn().PadLeft(1).PadRight(0));
|
||||
|
||||
// Inner
|
||||
if (ex.Inner != null)
|
||||
{
|
||||
grid.AddRow(
|
||||
Text.Empty,
|
||||
GetException(ex.Inner, settings));
|
||||
}
|
||||
|
||||
// Stack frames
|
||||
foreach (var frame in ex.Frames)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
// Method
|
||||
var shortenMethods = (settings.Format & ExceptionFormats.ShortenMethods) != 0;
|
||||
builder.Append(Emphasize(frame.Method, new[] { '.' }, settings.Style.Method, shortenMethods, settings));
|
||||
builder.Append('[').Append(settings.Style.Parenthesis.ToMarkup()).Append(']').Append('(').Append("[/]");
|
||||
AppendParameters(builder, frame, settings);
|
||||
builder.Append('[').Append(settings.Style.Parenthesis.ToMarkup()).Append(']').Append(')').Append("[/]");
|
||||
|
||||
if (frame.Path != null)
|
||||
{
|
||||
builder.Append(" [").Append(settings.Style.Dimmed.ToMarkup()).Append("]in[/] ");
|
||||
|
||||
// Path
|
||||
AppendPath(builder, frame, settings);
|
||||
|
||||
// Line number
|
||||
if (frame.LineNumber != null)
|
||||
{
|
||||
builder.Append('[').Append(settings.Style.Dimmed.ToMarkup()).Append("]:[/]");
|
||||
builder.Append('[').Append(settings.Style.LineNumber.ToMarkup()).Append(']').Append(frame.LineNumber).Append("[/]");
|
||||
}
|
||||
}
|
||||
|
||||
grid.AddRow(
|
||||
$"[{settings.Style.Dimmed.ToMarkup()}]at[/]",
|
||||
builder.ToString());
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static void AppendParameters(StringBuilder builder, StackFrameInfo frame, ExceptionSettings settings)
|
||||
{
|
||||
var typeColor = settings.Style.ParameterType.ToMarkup();
|
||||
var nameColor = settings.Style.ParameterName.ToMarkup();
|
||||
var parameters = frame.Parameters.Select(x => $"[{typeColor}]{x.Type.SafeMarkup()}[/] [{nameColor}]{x.Name}[/]");
|
||||
builder.Append(string.Join(", ", parameters));
|
||||
}
|
||||
|
||||
private static void AppendPath(StringBuilder builder, StackFrameInfo frame, ExceptionSettings settings)
|
||||
{
|
||||
if (frame?.Path is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AppendPath()
|
||||
{
|
||||
var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0;
|
||||
builder.Append(Emphasize(frame.Path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings));
|
||||
}
|
||||
|
||||
if ((settings.Format & ExceptionFormats.ShowLinks) != 0)
|
||||
{
|
||||
var hasLink = frame.TryGetUri(out var uri);
|
||||
if (hasLink && uri != null)
|
||||
{
|
||||
builder.Append("[link=").Append(uri.AbsoluteUri).Append(']');
|
||||
}
|
||||
|
||||
AppendPath();
|
||||
|
||||
if (hasLink && uri != null)
|
||||
{
|
||||
builder.Append("[/]");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppendPath();
|
||||
}
|
||||
}
|
||||
|
||||
private static string Emphasize(string input, char[] separators, Style color, bool compact, ExceptionSettings settings)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
var type = input;
|
||||
var index = type.LastIndexOfAny(separators);
|
||||
if (index != -1)
|
||||
{
|
||||
if (!compact)
|
||||
{
|
||||
builder.Append('[').Append(settings.Style.NonEmphasized.ToMarkup()).Append(']')
|
||||
.Append(type, 0, index + 1).Append("[/]");
|
||||
}
|
||||
|
||||
builder.Append('[').Append(color.ToMarkup()).Append(']')
|
||||
.Append(type, index + 1, type.Length - index - 1).Append("[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(type);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Spectre.Console/Internal/ExceptionInfo.cs
Normal file
23
src/Spectre.Console/Internal/ExceptionInfo.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class ExceptionInfo
|
||||
{
|
||||
public string Type { get; }
|
||||
public string Message { get; }
|
||||
public List<StackFrameInfo> Frames { get; }
|
||||
public ExceptionInfo? Inner { get; }
|
||||
|
||||
public ExceptionInfo(
|
||||
string type, string message,
|
||||
List<StackFrameInfo> frames,
|
||||
ExceptionInfo? inner)
|
||||
{
|
||||
Type = type ?? string.Empty;
|
||||
Message = message ?? string.Empty;
|
||||
Frames = frames ?? new List<StackFrameInfo>();
|
||||
Inner = inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/Spectre.Console/Internal/ExceptionParser.cs
Normal file
142
src/Spectre.Console/Internal/ExceptionParser.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class ExceptionParser
|
||||
{
|
||||
private static readonly Regex _messageRegex = new Regex(@"^(?'type'.*):\s(?'message'.*)$");
|
||||
private static readonly Regex _stackFrameRegex = new Regex(@"^\s*\w*\s(?'method'.*)\((?'params'.*)\)");
|
||||
private static readonly Regex _fullStackFrameRegex = new Regex(@"^\s*(?'at'\w*)\s(?'method'.*)\((?'params'.*)\)\s(?'in'\w*)\s(?'path'.*)\:(?'line'\w*)\s(?'linenumber'\d*)$");
|
||||
|
||||
public static ExceptionInfo? Parse(string exception)
|
||||
{
|
||||
if (exception is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
var lines = exception.SplitLines();
|
||||
return Parse(new Queue<string>(lines));
|
||||
}
|
||||
|
||||
private static ExceptionInfo? Parse(Queue<string> lines)
|
||||
{
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
// Error: No lines to parse
|
||||
return null;
|
||||
}
|
||||
|
||||
var line = lines.Dequeue();
|
||||
line = line.Replace(" ---> ", string.Empty);
|
||||
|
||||
var match = _messageRegex.Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var inner = (ExceptionInfo?)null;
|
||||
|
||||
// Stack frames
|
||||
var frames = new List<StackFrameInfo>();
|
||||
while (lines.Count > 0)
|
||||
{
|
||||
if (lines.Peek().TrimStart().StartsWith("---> ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
inner = Parse(lines);
|
||||
if (inner == null)
|
||||
{
|
||||
// Error: Could not parse inner exception
|
||||
return null;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
line = lines.Dequeue();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
// Empty line
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.TrimStart().StartsWith("--- ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// End of inner exception
|
||||
break;
|
||||
}
|
||||
|
||||
var stackFrame = ParseStackFrame(line);
|
||||
if (stackFrame == null)
|
||||
{
|
||||
// Error: Could not parse stack frame
|
||||
return null;
|
||||
}
|
||||
|
||||
frames.Add(stackFrame);
|
||||
}
|
||||
|
||||
return new ExceptionInfo(
|
||||
match.Groups["type"].Value,
|
||||
match.Groups["message"].Value,
|
||||
frames, inner);
|
||||
}
|
||||
|
||||
private static StackFrameInfo? ParseStackFrame(string frame)
|
||||
{
|
||||
var match = _fullStackFrameRegex.Match(frame);
|
||||
if (match?.Success != true)
|
||||
{
|
||||
match = _stackFrameRegex.Match(frame);
|
||||
if (match?.Success != true)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var parameters = ParseMethodParameters(match.Groups["params"].Value);
|
||||
if (parameters == null)
|
||||
{
|
||||
// Error: Could not parse parameters
|
||||
return null;
|
||||
}
|
||||
|
||||
var method = match.Groups["method"].Value;
|
||||
var path = match.Groups["path"].Success ? match.Groups["path"].Value : null;
|
||||
|
||||
var lineNumber = (int?)null;
|
||||
if (!string.IsNullOrWhiteSpace(match.Groups["linenumber"].Value))
|
||||
{
|
||||
lineNumber = int.Parse(match.Groups["linenumber"].Value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return new StackFrameInfo(method, parameters, path, lineNumber);
|
||||
}
|
||||
|
||||
private static List<(string Type, string Name)>? ParseMethodParameters(string parameters)
|
||||
{
|
||||
var result = new List<(string Type, string Name)>();
|
||||
foreach (var parameterPart in parameters.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var parameterNameIndex = parameterPart.LastIndexOf(' ');
|
||||
if (parameterNameIndex == -1)
|
||||
{
|
||||
// Error: Could not parse parameter
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = parameterPart.Substring(0, parameterNameIndex);
|
||||
var name = parameterPart.Substring(parameterNameIndex + 1, parameterPart.Length - parameterNameIndex - 1);
|
||||
|
||||
result.Add((type, name));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class DictionaryExtensions
|
||||
{
|
||||
public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple, out T1 key, out T2 value)
|
||||
{
|
||||
key = tuple.Key;
|
||||
value = tuple.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ namespace Spectre.Console.Internal
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public static string Multiply(this string text, int count)
|
||||
public static string Repeat(this string text, int count)
|
||||
{
|
||||
if (text is null)
|
||||
{
|
||||
|
||||
65
src/Spectre.Console/Internal/StackFrameInfo.cs
Normal file
65
src/Spectre.Console/Internal/StackFrameInfo.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class StackFrameInfo
|
||||
{
|
||||
public string Method { get; }
|
||||
public List<(string Type, string Name)> Parameters { get; }
|
||||
public string? Path { get; }
|
||||
public int? LineNumber { get; }
|
||||
|
||||
public StackFrameInfo(
|
||||
string method, List<(string Type, string Name)> parameters,
|
||||
string? path, int? lineNumber)
|
||||
{
|
||||
Method = method ?? throw new System.ArgumentNullException(nameof(method));
|
||||
Parameters = parameters ?? throw new System.ArgumentNullException(nameof(parameters));
|
||||
Path = path;
|
||||
LineNumber = lineNumber;
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public bool TryGetUri([NotNullWhen(true)] out Uri? result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Path == null)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(Path, UriKind.Absolute, out var uri))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.Scheme == "file")
|
||||
{
|
||||
// For local files, we need to append
|
||||
// the host name. Otherwise the terminal
|
||||
// will most probably not allow it.
|
||||
var builder = new UriBuilder(uri)
|
||||
{
|
||||
Host = Dns.GetHostName(),
|
||||
};
|
||||
|
||||
uri = builder.Uri;
|
||||
}
|
||||
|
||||
result = uri;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class AsciiBoxBorder : BoxBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(BoxBorderPart part)
|
||||
public override string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class DoubleBoxBorder : BoxBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(BoxBorderPart part)
|
||||
public override string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override BoxBorder? SafeBorder => BoxBorder.Square;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(BoxBorderPart part)
|
||||
public override string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class NoBoxBorder : BoxBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(BoxBorderPart part)
|
||||
public override string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return " ";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override BoxBorder? SafeBorder => BoxBorder.Square;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(BoxBorderPart part)
|
||||
public override string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class SquareBoxBorder : BoxBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(BoxBorderPart part)
|
||||
public override string GetPart(BoxBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class Ascii2TableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class AsciiDoubleHeadTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class AsciiTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class DoubleEdgeTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class DoubleTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override TableBorder? SafeBorder => TableBorder.Square;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override TableBorder? SafeBorder => TableBorder.Square;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override TableBorder? SafeBorder => TableBorder.Square;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class HorizontalTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class MarkdownTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
@@ -57,39 +57,39 @@ namespace Spectre.Console.Rendering
|
||||
if (padding.Left > 0)
|
||||
{
|
||||
// Left padding
|
||||
builder.Append(" ".Multiply(padding.Left));
|
||||
builder.Append(" ".Repeat(padding.Left));
|
||||
}
|
||||
|
||||
var justification = columns[columnIndex].Alignment;
|
||||
if (justification == null)
|
||||
{
|
||||
// No alignment
|
||||
builder.Append(center.Multiply(columnWidth));
|
||||
builder.Append(center.Repeat(columnWidth));
|
||||
}
|
||||
else if (justification.Value == Justify.Left)
|
||||
{
|
||||
// Left
|
||||
builder.Append(':');
|
||||
builder.Append(center.Multiply(columnWidth - 1));
|
||||
builder.Append(center.Repeat(columnWidth - 1));
|
||||
}
|
||||
else if (justification.Value == Justify.Center)
|
||||
{
|
||||
// Centered
|
||||
builder.Append(':');
|
||||
builder.Append(center.Multiply(columnWidth - 2));
|
||||
builder.Append(center.Repeat(columnWidth - 2));
|
||||
builder.Append(':');
|
||||
}
|
||||
else if (justification.Value == Justify.Right)
|
||||
{
|
||||
// Right
|
||||
builder.Append(center.Multiply(columnWidth - 1));
|
||||
builder.Append(center.Repeat(columnWidth - 1));
|
||||
builder.Append(':');
|
||||
}
|
||||
|
||||
// Right padding
|
||||
if (padding.Right > 0)
|
||||
{
|
||||
builder.Append(" ".Multiply(padding.Right));
|
||||
builder.Append(" ".Repeat(padding.Right));
|
||||
}
|
||||
|
||||
if (!lastColumn)
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class MinimalDoubleHeadTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override TableBorder? SafeBorder => TableBorder.Minimal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class MinimalTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Spectre.Console.Rendering
|
||||
public override bool Visible => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return " ";
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override TableBorder? SafeBorder => TableBorder.Square;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Spectre.Console.Rendering
|
||||
public override TableBorder? SafeBorder => TableBorder.Simple;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class SimpleTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Spectre.Console.Rendering
|
||||
public sealed class SquareTableBorder : TableBorder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string GetBorderPart(TableBorderPart part)
|
||||
public override string GetPart(TableBorderPart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace Spectre.Console.Rendering
|
||||
}
|
||||
|
||||
// Same style?
|
||||
if (previous.Style.Equals(segment.Style))
|
||||
if (previous.Style.Equals(segment.Style) && !previous.IsLineBreak)
|
||||
{
|
||||
previous = new Segment(previous.Text + segment.Text, previous.Style);
|
||||
}
|
||||
@@ -299,7 +299,15 @@ namespace Spectre.Console.Rendering
|
||||
while (lengthLeft > 0)
|
||||
{
|
||||
var index = totalLength - lengthLeft;
|
||||
|
||||
// How many characters should we take?
|
||||
var take = Math.Min(width, totalLength - index);
|
||||
if (take == 0)
|
||||
{
|
||||
// This shouldn't really occur, but I don't like
|
||||
// never ending loops if it does...
|
||||
return new List<Segment>();
|
||||
}
|
||||
|
||||
result.Add(new Segment(segment.Text.Substring(index, take), segment.Style));
|
||||
lengthLeft -= take;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
@@ -25,7 +27,7 @@ namespace Spectre.Console
|
||||
public Decoration Decoration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the link.
|
||||
/// Gets the link associated with the style.
|
||||
/// </summary>
|
||||
public string? Link { get; }
|
||||
|
||||
@@ -191,6 +193,41 @@ namespace Spectre.Console
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the markup representation of this style.
|
||||
/// </summary>
|
||||
/// <returns>The markup representation of this style.</returns>
|
||||
public string ToMarkup()
|
||||
{
|
||||
var builder = new List<string>();
|
||||
|
||||
if (Decoration != Decoration.None)
|
||||
{
|
||||
var result = DecorationTable.GetMarkupNames(Decoration);
|
||||
if (result.Count != 0)
|
||||
{
|
||||
builder.AddRange(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (Foreground != Color.Default)
|
||||
{
|
||||
builder.Add(Foreground.ToMarkup());
|
||||
}
|
||||
|
||||
if (Background != Color.Default)
|
||||
{
|
||||
if (builder.Count == 0)
|
||||
{
|
||||
builder.Add("default");
|
||||
}
|
||||
|
||||
builder.Add("on " + Background.ToMarkup());
|
||||
}
|
||||
|
||||
return string.Join(" ", builder);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Internal;
|
||||
using Spectre.Console.Rendering;
|
||||
@@ -13,8 +11,6 @@ namespace Spectre.Console
|
||||
/// </summary>
|
||||
public abstract partial class TableBorder
|
||||
{
|
||||
private readonly Dictionary<TableBorderPart, string> _lookup;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the border is visible.
|
||||
/// </summary>
|
||||
@@ -26,46 +22,11 @@ namespace Spectre.Console
|
||||
public virtual TableBorder? SafeBorder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TableBorder"/> class.
|
||||
/// Gets the string representation of a specified table border part.
|
||||
/// </summary>
|
||||
protected TableBorder()
|
||||
{
|
||||
_lookup = Initialize();
|
||||
}
|
||||
|
||||
private Dictionary<TableBorderPart, string> Initialize()
|
||||
{
|
||||
var lookup = new Dictionary<TableBorderPart, string>();
|
||||
foreach (TableBorderPart? part in Enum.GetValues(typeof(TableBorderPart)))
|
||||
{
|
||||
if (part == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = GetBorderPart(part.Value);
|
||||
if (text.Length > 1)
|
||||
{
|
||||
throw new InvalidOperationException("A box part cannot contain more than one character.");
|
||||
}
|
||||
|
||||
lookup.Add(part.Value, GetBorderPart(part.Value));
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <param name="count">The number of repetitions.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(TableBorderPart part, int count)
|
||||
{
|
||||
// TODO: This need some optimization...
|
||||
return string.Join(string.Empty, Enumerable.Repeat(GetBorderPart(part)[0], count));
|
||||
}
|
||||
/// <param name="part">The part to get the character representation for.</param>
|
||||
/// <returns>A character representation of the specified border part.</returns>
|
||||
public abstract string GetPart(TableBorderPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a whole column row for the specific column row part.
|
||||
@@ -95,7 +56,7 @@ namespace Spectre.Console
|
||||
{
|
||||
var padding = columns[columnIndex].Padding;
|
||||
var centerWidth = padding.Left + columnWidth + padding.Right;
|
||||
builder.Append(center.Multiply(centerWidth));
|
||||
builder.Append(center.Repeat(centerWidth));
|
||||
|
||||
if (!lastColumn)
|
||||
{
|
||||
@@ -107,30 +68,12 @@ namespace Spectre.Console
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string representation of a specific border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get a string representation for.</param>
|
||||
/// <returns>A string representation of the specified border part.</returns>
|
||||
public string GetPart(TableBorderPart part)
|
||||
{
|
||||
return _lookup[part].ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the character representing the specified border part.
|
||||
/// </summary>
|
||||
/// <param name="part">The part to get the character representation for.</param>
|
||||
/// <returns>A character representation of the specified border part.</returns>
|
||||
protected abstract string GetBorderPart(TableBorderPart part);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the table parts used to render a specific table row.
|
||||
/// </summary>
|
||||
/// <param name="part">The table part.</param>
|
||||
/// <returns>The table parts used to render the specific table row.</returns>
|
||||
protected (string Left, string Center, string Separator, string Right)
|
||||
GetTableParts(TablePart part)
|
||||
protected (string Left, string Center, string Separator, string Right) GetTableParts(TablePart part)
|
||||
{
|
||||
return part switch
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Spectre.Console.Internal;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
@@ -93,8 +94,15 @@ namespace Spectre.Console
|
||||
|
||||
// Split the child segments into lines.
|
||||
var childSegments = ((IRenderable)child).Render(context, childWidth);
|
||||
foreach (var line in Segment.SplitLines(childSegments, panelWidth))
|
||||
foreach (var line in Segment.SplitLines(childSegments, childWidth))
|
||||
{
|
||||
if (line.Count == 1 && line[0].IsWhiteSpace)
|
||||
{
|
||||
// NOTE: This check might impact other things.
|
||||
// Hopefully not, but there is a chance.
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.Left), borderStyle));
|
||||
|
||||
var content = new List<Segment>();
|
||||
@@ -123,7 +131,7 @@ namespace Spectre.Console
|
||||
private static void AddBottomBorder(List<Segment> result, BoxBorder border, Style borderStyle, int panelWidth)
|
||||
{
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.BottomLeft), borderStyle));
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.Bottom, panelWidth - EdgeWidth), borderStyle));
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.Bottom).Repeat(panelWidth - EdgeWidth), borderStyle));
|
||||
result.Add(new Segment(border.GetPart(BoxBorderPart.BottomRight), borderStyle));
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
@@ -160,13 +168,13 @@ namespace Spectre.Console
|
||||
}
|
||||
}
|
||||
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.Top, leftSpacing + 1), borderStyle));
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.Top).Repeat(leftSpacing + 1), borderStyle));
|
||||
segments.Add(header);
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.Top, rightSpacing + 1), borderStyle));
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.Top).Repeat(rightSpacing + 1), borderStyle));
|
||||
}
|
||||
else
|
||||
{
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.Top, panelWidth - EdgeWidth), borderStyle));
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.Top).Repeat(panelWidth - EdgeWidth), borderStyle));
|
||||
}
|
||||
|
||||
segments.Add(new Segment(border.GetPart(BoxBorderPart.TopRight), borderStyle));
|
||||
|
||||
@@ -227,17 +227,10 @@ namespace Spectre.Console
|
||||
throw new InvalidOperationException("Iterator returned empty segment.");
|
||||
}
|
||||
|
||||
if (newLine && current.IsWhiteSpace && !current.IsLineBreak)
|
||||
{
|
||||
newLine = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
newLine = false;
|
||||
|
||||
if (current.IsLineBreak)
|
||||
{
|
||||
line.Add(current);
|
||||
lines.Add(line);
|
||||
line = new SegmentLine();
|
||||
newLine = true;
|
||||
|
||||
@@ -44,22 +44,26 @@ namespace Spectre.Console
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||
{
|
||||
var result = new List<Segment>();
|
||||
|
||||
foreach (var child in _children)
|
||||
{
|
||||
var segments = child.Render(context, maxWidth);
|
||||
foreach (var (_, _, last, segment) in segments.Enumerate())
|
||||
{
|
||||
yield return segment;
|
||||
result.Add(segment);
|
||||
|
||||
if (last)
|
||||
{
|
||||
if (!segment.IsLineBreak)
|
||||
{
|
||||
yield return Segment.LineBreak;
|
||||
result.Add(Segment.LineBreak);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +298,6 @@ namespace Spectre.Console
|
||||
var widths = width_ranges.Select(range => range.Max).ToList();
|
||||
|
||||
var tableWidth = widths.Sum();
|
||||
|
||||
if (tableWidth > maxWidth)
|
||||
{
|
||||
var wrappable = _columns.Select(c => !c.NoWrap).ToList();
|
||||
|
||||
Reference in New Issue
Block a user