mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
697273917e | ||
|
|
2943535973 | ||
|
|
cd0d182f12 | ||
|
|
b197f278ed | ||
|
|
3847a8949f |
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
using Spectre.Console.Tests.Tools;
|
using Spectre.Console.Tests.Tools;
|
||||||
|
|
||||||
namespace Spectre.Console.Tests
|
namespace Spectre.Console.Tests
|
||||||
@@ -36,9 +37,9 @@ namespace Spectre.Console.Tests
|
|||||||
_writer?.Dispose();
|
_writer?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(string text, Style style)
|
public void Write(Segment segment)
|
||||||
{
|
{
|
||||||
_console.Write(text, style);
|
_console.Write(segment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace Spectre.Console.Tests
|
namespace Spectre.Console.Tests
|
||||||
{
|
{
|
||||||
@@ -40,9 +41,14 @@ namespace Spectre.Console.Tests
|
|||||||
Writer.Dispose();
|
Writer.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(string text, Style style)
|
public void Write(Segment segment)
|
||||||
{
|
{
|
||||||
Writer.Write(text);
|
if (segment is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(segment));
|
||||||
|
}
|
||||||
|
|
||||||
|
Writer.Write(segment.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,5 +36,21 @@ namespace Spectre.Console.Tests.Unit
|
|||||||
// Then
|
// Then
|
||||||
console.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/Spectre.Console.Tests/Unit/RecorderTests.cs
Normal file
66
src/Spectre.Console.Tests/Unit/RecorderTests.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit
|
||||||
|
{
|
||||||
|
public sealed class RecorderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Export_Text_As_Expected()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
var recorder = new Recorder(console);
|
||||||
|
|
||||||
|
recorder.Render(new Table()
|
||||||
|
.AddColumns("Foo", "Bar", "Qux")
|
||||||
|
.AddRow("Corgi", "Waldo", "Zap")
|
||||||
|
.AddRow(new Panel("Hello World").RoundedBorder()));
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = recorder.ExportText().Split(new[] { '\n' });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Length.ShouldBe(8);
|
||||||
|
result[0].ShouldBe("┌─────────────────┬───────┬─────┐");
|
||||||
|
result[1].ShouldBe("│ Foo │ Bar │ Qux │");
|
||||||
|
result[2].ShouldBe("├─────────────────┼───────┼─────┤");
|
||||||
|
result[3].ShouldBe("│ Corgi │ Waldo │ Zap │");
|
||||||
|
result[4].ShouldBe("│ ╭─────────────╮ │ │ │");
|
||||||
|
result[5].ShouldBe("│ │ Hello World │ │ │ │");
|
||||||
|
result[6].ShouldBe("│ ╰─────────────╯ │ │ │");
|
||||||
|
result[7].ShouldBe("└─────────────────┴───────┴─────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Export_Html_As_Expected()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole();
|
||||||
|
var recorder = new Recorder(console);
|
||||||
|
|
||||||
|
recorder.Render(new Table()
|
||||||
|
.AddColumns("[red on black]Foo[/]", "[green bold]Bar[/]", "[blue italic]Qux[/]")
|
||||||
|
.AddRow("[invert underline]Corgi[/]", "[bold strikethrough]Waldo[/]", "[dim]Zap[/]")
|
||||||
|
.AddRow(new Panel("[blue]Hello World[/]")
|
||||||
|
.SetBorderColor(Color.Red).RoundedBorder()));
|
||||||
|
|
||||||
|
// When
|
||||||
|
var html = recorder.ExportHtml();
|
||||||
|
var result = html.Split(new[] { '\n' });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Length.ShouldBe(10);
|
||||||
|
result[0].ShouldBe("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">");
|
||||||
|
result[1].ShouldBe("<span>┌─────────────────┬───────┬─────┐</span>");
|
||||||
|
result[2].ShouldBe("<span>│ </span><span style=\"color: #FF0000;background-color: #000000\">Foo</span><span> │ </span><span style=\"color: #008000;font-weight: bold;font-style: italic\">Bar</span><span> │ </span><span style=\"color: #0000FF\">Qux</span><span> │</span>");
|
||||||
|
result[3].ShouldBe("<span>├─────────────────┼───────┼─────┤</span>");
|
||||||
|
result[4].ShouldBe("<span>│ </span><span style=\"text-decoration: underline\">Corgi</span><span> │ </span><span style=\"font-weight: bold;font-style: italic;text-decoration: line-through\">Waldo</span><span> │ </span><span style=\"color: #7F7F7F\">Zap</span><span> │</span>");
|
||||||
|
result[5].ShouldBe("<span>│ </span><span style=\"color: #FF0000\">╭─────────────╮</span><span> │ │ │</span>");
|
||||||
|
result[6].ShouldBe("<span>│ </span><span style=\"color: #FF0000\">│</span><span> </span><span style=\"color: #0000FF\">Hello World</span><span> </span><span style=\"color: #FF0000\">│</span><span> │ │ │</span>");
|
||||||
|
result[7].ShouldBe("<span>│ </span><span style=\"color: #FF0000\">╰─────────────╯</span><span> │ │ │</span>");
|
||||||
|
result[8].ShouldBe("<span>└─────────────────┴───────┴─────┘</span>");
|
||||||
|
result[9].ShouldBe("</pre>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/Spectre.Console.Tests/Unit/RowsTests.cs
Normal file
96
src/Spectre.Console.Tests/Unit/RowsTests.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using Shouldly;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit
|
||||||
|
{
|
||||||
|
public sealed class RowsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Rows()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 60);
|
||||||
|
var rows = new Rows(
|
||||||
|
new IRenderable[]
|
||||||
|
{
|
||||||
|
new Markup("Hello"),
|
||||||
|
new Table()
|
||||||
|
.AddColumns("Foo", "Bar")
|
||||||
|
.AddRow("Baz", "Qux"),
|
||||||
|
new Markup("World"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(rows);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(7);
|
||||||
|
console.Lines[0].ShouldBe("Hello");
|
||||||
|
console.Lines[1].ShouldBe("┌─────┬─────┐");
|
||||||
|
console.Lines[2].ShouldBe("│ Foo │ Bar │");
|
||||||
|
console.Lines[3].ShouldBe("├─────┼─────┤");
|
||||||
|
console.Lines[4].ShouldBe("│ Baz │ Qux │");
|
||||||
|
console.Lines[5].ShouldBe("└─────┴─────┘");
|
||||||
|
console.Lines[6].ShouldBe("World");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Rows_Correctly_Inside_Other_Widget()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 60);
|
||||||
|
var table = new Table()
|
||||||
|
.AddColumns("Foo", "Bar")
|
||||||
|
.AddRow("HELLO WORLD")
|
||||||
|
.AddRow(
|
||||||
|
new Rows(new IRenderable[]
|
||||||
|
{
|
||||||
|
new Markup("Hello"),
|
||||||
|
new Markup("World"),
|
||||||
|
}), new Text("Qux"));
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(7);
|
||||||
|
console.Lines[0].ShouldBe("┌─────────────┬─────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │");
|
||||||
|
console.Lines[2].ShouldBe("├─────────────┼─────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ HELLO WORLD │ │");
|
||||||
|
console.Lines[4].ShouldBe("│ Hello │ Qux │");
|
||||||
|
console.Lines[5].ShouldBe("│ World │ │");
|
||||||
|
console.Lines[6].ShouldBe("└─────────────┴─────┘");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Render_Rows_Correctly_Inside_Other_Widget_When_Expanded()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new PlainConsole(width: 60);
|
||||||
|
var table = new Table()
|
||||||
|
.AddColumns("Foo", "Bar")
|
||||||
|
.AddRow("HELLO WORLD")
|
||||||
|
.AddRow(
|
||||||
|
new Rows(new IRenderable[]
|
||||||
|
{
|
||||||
|
new Markup("Hello"),
|
||||||
|
new Markup("World"),
|
||||||
|
}).Expand(), new Text("Qux"));
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Render(table);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
console.Lines.Count.ShouldBe(7);
|
||||||
|
console.Lines[0].ShouldBe("┌────────────────────────────────────────────────────┬─────┐");
|
||||||
|
console.Lines[1].ShouldBe("│ Foo │ Bar │");
|
||||||
|
console.Lines[2].ShouldBe("├────────────────────────────────────────────────────┼─────┤");
|
||||||
|
console.Lines[3].ShouldBe("│ HELLO WORLD │ │");
|
||||||
|
console.Lines[4].ShouldBe("│ Hello │ Qux │");
|
||||||
|
console.Lines[5].ShouldBe("│ World │ │");
|
||||||
|
console.Lines[6].ShouldBe("└────────────────────────────────────────────────────┴─────┘");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/Spectre.Console/AnsiConsole.Recording.cs
Normal file
67
src/Spectre.Console/AnsiConsole.Recording.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A console capable of writing ANSI escape sequences.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class AnsiConsole
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Starts recording the console output.
|
||||||
|
/// </summary>
|
||||||
|
public static void Record()
|
||||||
|
{
|
||||||
|
_recorder = new Recorder(_console.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports all recorded console output as text.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The recorded output as text.</returns>
|
||||||
|
public static string ExportText()
|
||||||
|
{
|
||||||
|
if (_recorder == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot export text since a recording hasn't been started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _recorder.ExportText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports all recorded console output as HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The recorded output as HTML.</returns>
|
||||||
|
public static string ExportHtml()
|
||||||
|
{
|
||||||
|
if (_recorder == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _recorder.ExportHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports all recorded console output using a custom encoder.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoder">The encoder to use.</param>
|
||||||
|
/// <returns>The recorded output.</returns>
|
||||||
|
public static string ExportCustom(IAnsiConsoleEncoder encoder)
|
||||||
|
{
|
||||||
|
if (_recorder == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _recorder.Export(encoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,10 +21,12 @@ namespace Spectre.Console
|
|||||||
return console;
|
return console;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private static Recorder? _recorder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the underlying <see cref="IAnsiConsole"/>.
|
/// Gets the underlying <see cref="IAnsiConsole"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IAnsiConsole Console => _console.Value;
|
public static IAnsiConsole Console => _recorder ?? _console.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the console's capabilities.
|
/// Gets the console's capabilities.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using Spectre.Console.Internal;
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
@@ -60,6 +61,35 @@ namespace Spectre.Console
|
|||||||
Number = null;
|
Number = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blends two colors.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The other color.</param>
|
||||||
|
/// <param name="factor">The blend factor.</param>
|
||||||
|
/// <returns>The resulting color.</returns>
|
||||||
|
public Color Blend(Color other, float factor)
|
||||||
|
{
|
||||||
|
// https://github.com/willmcgugan/rich/blob/f092b1d04252e6f6812021c0f415dd1d7be6a16a/rich/color.py#L494
|
||||||
|
return new Color(
|
||||||
|
(byte)(R + ((other.R - R) * factor)),
|
||||||
|
(byte)(G + ((other.G - G) * factor)),
|
||||||
|
(byte)(B + ((other.B - B) * factor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the hexadecimal representation of the color.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The hexadecimal representation of the color.</returns>
|
||||||
|
public string ToHex()
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}{1}{2}",
|
||||||
|
R.ToString("X2", CultureInfo.InvariantCulture),
|
||||||
|
G.ToString("X2", CultureInfo.InvariantCulture),
|
||||||
|
B.ToString("X2", CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
{
|
{
|
||||||
@@ -7,6 +8,32 @@ namespace Spectre.Console
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static partial class AnsiConsoleExtensions
|
public static partial class AnsiConsoleExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a recorder for the specified console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console to record.</param>
|
||||||
|
/// <returns>A recorder for the specified console.</returns>
|
||||||
|
public static Recorder CreateRecorder(this IAnsiConsole console)
|
||||||
|
{
|
||||||
|
return new Recorder(console);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the specified string value to the console.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console to write to.</param>
|
||||||
|
/// <param name="text">The text to write.</param>
|
||||||
|
/// <param name="style">The text style.</param>
|
||||||
|
public static void Write(this IAnsiConsole console, string text, Style style)
|
||||||
|
{
|
||||||
|
if (console is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(console));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.Write(new Segment(text, style));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes an empty line to the console.
|
/// Writes an empty line to the console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,7 +61,7 @@ namespace Spectre.Console
|
|||||||
throw new ArgumentNullException(nameof(console));
|
throw new ArgumentNullException(nameof(console));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.Write(text, style);
|
console.Write(new Segment(text, style));
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
src/Spectre.Console/Extensions/RecorderExtensions.cs
Normal file
44
src/Spectre.Console/Extensions/RecorderExtensions.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for <see cref="Recorder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class RecorderExtensions
|
||||||
|
{
|
||||||
|
private static readonly TextEncoder _textEncoder = new TextEncoder();
|
||||||
|
private static readonly HtmlEncoder _htmlEncoder = new HtmlEncoder();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports the recorded content as text.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="recorder">The recorder.</param>
|
||||||
|
/// <returns>The recorded content as text.</returns>
|
||||||
|
public static string ExportText(this Recorder recorder)
|
||||||
|
{
|
||||||
|
if (recorder is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(recorder));
|
||||||
|
}
|
||||||
|
|
||||||
|
return recorder.Export(_textEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports the recorded content as HTML.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="recorder">The recorder.</param>
|
||||||
|
/// <returns>The recorded content as HTML.</returns>
|
||||||
|
public static string ExportHtml(this Recorder recorder)
|
||||||
|
{
|
||||||
|
if (recorder is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(recorder));
|
||||||
|
}
|
||||||
|
|
||||||
|
return recorder.Export(_htmlEncoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace Spectre.Console
|
namespace Spectre.Console
|
||||||
{
|
{
|
||||||
@@ -30,8 +31,7 @@ namespace Spectre.Console
|
|||||||
/// <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="segment">The segment to write.</param>
|
||||||
/// <param name="style">The style to use.</param>
|
void Write(Segment segment);
|
||||||
void Write(string text, Style style);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace Spectre.Console.Internal
|
namespace Spectre.Console.Internal
|
||||||
{
|
{
|
||||||
@@ -48,21 +49,14 @@ namespace Spectre.Console.Internal
|
|||||||
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
|
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(string text, Style style)
|
public void Write(Segment segment)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(text))
|
var parts = segment.Text.NormalizeLineEndings().Split(new[] { '\n' });
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
style ??= Style.Plain;
|
|
||||||
|
|
||||||
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(part, style));
|
_out.Write(_ansiBuilder.GetAnsi(part, segment.Style));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!last)
|
if (!last)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace Spectre.Console.Internal
|
namespace Spectre.Console.Internal
|
||||||
{
|
{
|
||||||
@@ -61,14 +62,14 @@ namespace Spectre.Console.Internal
|
|||||||
Capabilities = capabilities;
|
Capabilities = capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(string text, Style style)
|
public void Write(Segment segment)
|
||||||
{
|
{
|
||||||
if (_lastStyle?.Equals(style) != true)
|
if (_lastStyle?.Equals(segment.Style) != true)
|
||||||
{
|
{
|
||||||
SetStyle(style);
|
SetStyle(segment.Style);
|
||||||
}
|
}
|
||||||
|
|
||||||
_out.Write(text.NormalizeLineEndings(native: true));
|
_out.Write(segment.Text.NormalizeLineEndings(native: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStyle(Style style)
|
private void SetStyle(Style style)
|
||||||
|
|||||||
114
src/Spectre.Console/Internal/HtmlEncoder.cs
Normal file
114
src/Spectre.Console/Internal/HtmlEncoder.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Internal
|
||||||
|
{
|
||||||
|
internal sealed class HtmlEncoder : IAnsiConsoleEncoder
|
||||||
|
{
|
||||||
|
public string Encode(IEnumerable<Segment> segments)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n");
|
||||||
|
|
||||||
|
foreach (var (_, first, _, segment) in segments.Enumerate())
|
||||||
|
{
|
||||||
|
if (segment.Text == "\n" && !first)
|
||||||
|
{
|
||||||
|
builder.Append('\n');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = segment.Text.Split(new[] { '\n' }, StringSplitOptions.None);
|
||||||
|
foreach (var (_, _, last, line) in parts.Enumerate())
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append("<span");
|
||||||
|
if (!segment.Style.Equals(Style.Plain))
|
||||||
|
{
|
||||||
|
builder.Append(" style=\"");
|
||||||
|
builder.Append(BuildCss(segment.Style));
|
||||||
|
builder.Append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append('>');
|
||||||
|
builder.Append(line);
|
||||||
|
builder.Append("</span>");
|
||||||
|
|
||||||
|
if (parts.Length > 1 && !last)
|
||||||
|
{
|
||||||
|
builder.Append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append("</pre>");
|
||||||
|
|
||||||
|
return builder.ToString().TrimEnd('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildCss(Style style)
|
||||||
|
{
|
||||||
|
var css = new List<string>();
|
||||||
|
|
||||||
|
var foreground = style.Foreground;
|
||||||
|
var background = style.Background;
|
||||||
|
|
||||||
|
if ((style.Decoration & Decoration.Invert) != 0)
|
||||||
|
{
|
||||||
|
var temp = foreground;
|
||||||
|
foreground = background;
|
||||||
|
background = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((style.Decoration & Decoration.Dim) != 0)
|
||||||
|
{
|
||||||
|
var blender = background;
|
||||||
|
if (blender.Equals(Color.Default))
|
||||||
|
{
|
||||||
|
blender = Color.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreground = foreground.Blend(blender, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foreground.Equals(Color.Default))
|
||||||
|
{
|
||||||
|
css.Add($"color: #{foreground.ToHex()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!background.Equals(Color.Default))
|
||||||
|
{
|
||||||
|
css.Add($"background-color: #{background.ToHex()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((style.Decoration & Decoration.Bold) != 0)
|
||||||
|
{
|
||||||
|
css.Add("font-weight: bold");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((style.Decoration & Decoration.Bold) != 0)
|
||||||
|
{
|
||||||
|
css.Add("font-style: italic");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((style.Decoration & Decoration.Underline) != 0)
|
||||||
|
{
|
||||||
|
css.Add("text-decoration: underline");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((style.Decoration & Decoration.Strikethrough) != 0)
|
||||||
|
{
|
||||||
|
css.Add("text-decoration: line-through");
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(";", css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ namespace Spectre.Console.Internal
|
|||||||
throw new ArgumentNullException(nameof(text));
|
throw new ArgumentNullException(nameof(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
text = Emoji.Replace(text);
|
|
||||||
style ??= Style.Plain;
|
style ??= Style.Plain;
|
||||||
|
|
||||||
var result = new Paragraph();
|
var result = new Paragraph();
|
||||||
@@ -47,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
|
||||||
{
|
{
|
||||||
|
|||||||
21
src/Spectre.Console/Internal/TextEncoder.cs
Normal file
21
src/Spectre.Console/Internal/TextEncoder.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Internal
|
||||||
|
{
|
||||||
|
internal sealed class TextEncoder : IAnsiConsoleEncoder
|
||||||
|
{
|
||||||
|
public string Encode(IEnumerable<Segment> segments)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var segment in Segment.Merge(segments))
|
||||||
|
{
|
||||||
|
builder.Append(segment.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString().TrimEnd('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/Spectre.Console/Recorder.cs
Normal file
66
src/Spectre.Console/Recorder.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A console recorder used to record output from a console.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Recorder : IAnsiConsole, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IAnsiConsole _console;
|
||||||
|
private readonly List<Segment> _recorded;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Capabilities Capabilities => _console.Capabilities;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Encoding Encoding => _console.Encoding;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Width => _console.Width;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Height => _console.Height;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Recorder"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="console">The console to record output for.</param>
|
||||||
|
public Recorder(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
_console = console ?? throw new ArgumentNullException(nameof(console));
|
||||||
|
_recorded = new List<Segment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Only used for scoping.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Write(Segment segment)
|
||||||
|
{
|
||||||
|
_recorded.Add(segment);
|
||||||
|
_console.Write(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports the recorded data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encoder">The encoder.</param>
|
||||||
|
/// <returns>The recorded data represented as a string.</returns>
|
||||||
|
public string Export(IAnsiConsoleEncoder encoder)
|
||||||
|
{
|
||||||
|
if (encoder is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encoder));
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoder.Encode(_recorded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Spectre.Console/Rendering/IAnsiConsoleEncoder.cs
Normal file
18
src/Spectre.Console/Rendering/IAnsiConsoleEncoder.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Rendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a console encoder that can encode
|
||||||
|
/// recorded segments into a string.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAnsiConsoleEncoder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encodes the specified segments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="segments">The segments to encode.</param>
|
||||||
|
/// <returns>The encoded string.</returns>
|
||||||
|
string Encode(IEnumerable<Segment> segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace Spectre.Console.Rendering
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the segment text.
|
/// Gets the segment text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Text { get; private set; }
|
public string Text { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not this is an expicit line break
|
/// Gets a value indicating whether or not this is an expicit line break
|
||||||
@@ -38,12 +38,12 @@ namespace Spectre.Console.Rendering
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a segment representing a line break.
|
/// Gets a segment representing a line break.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Segment LineBreak => new Segment(Environment.NewLine, Style.Plain, true);
|
public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an empty segment.
|
/// Gets an empty segment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Segment Empty => new Segment(string.Empty, Style.Plain, false);
|
public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain, false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Segment"/> class.
|
/// Initializes a new instance of the <see cref="Segment"/> class.
|
||||||
@@ -72,7 +72,7 @@ namespace Spectre.Console.Rendering
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text = text.NormalizeLineEndings();
|
Text = text.NormalizeLineEndings();
|
||||||
Style = style;
|
Style = style ?? throw new ArgumentNullException(nameof(style));
|
||||||
IsLineBreak = lineBreak;
|
IsLineBreak = lineBreak;
|
||||||
IsWhiteSpace = string.IsNullOrWhiteSpace(text);
|
IsWhiteSpace = string.IsNullOrWhiteSpace(text);
|
||||||
}
|
}
|
||||||
@@ -241,12 +241,10 @@ namespace Spectre.Console.Rendering
|
|||||||
// Same style?
|
// Same style?
|
||||||
if (previous.Style.Equals(segment.Style))
|
if (previous.Style.Equals(segment.Style))
|
||||||
{
|
{
|
||||||
// Modify the content of the previous segment
|
previous = new Segment(previous.Text + segment.Text, previous.Style);
|
||||||
previous.Text += segment.Text;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Push the current one to the results.
|
|
||||||
result.Add(previous);
|
result.Add(previous);
|
||||||
previous = segment;
|
previous = segment;
|
||||||
}
|
}
|
||||||
@@ -260,6 +258,15 @@ namespace Spectre.Console.Rendering
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clones the segment.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new segment that's identical to this one.</returns>
|
||||||
|
public Segment Clone()
|
||||||
|
{
|
||||||
|
return new Segment(Text, Style);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Splits an overflowing segment into several new segments.
|
/// Splits an overflowing segment into several new segments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
65
src/Spectre.Console/Widgets/Rows.cs
Normal file
65
src/Spectre.Console/Widgets/Rows.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Spectre.Console.Internal;
|
||||||
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
|
namespace Spectre.Console
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Renders things in rows.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Rows : Renderable, IExpandable
|
||||||
|
{
|
||||||
|
private readonly List<IRenderable> _children;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Expand { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Rows"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="children">The children to render.</param>
|
||||||
|
public Rows(IEnumerable<IRenderable> children)
|
||||||
|
{
|
||||||
|
_children = new List<IRenderable>(children ?? throw new ArgumentNullException(nameof(children)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override Measurement Measure(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
if (Expand)
|
||||||
|
{
|
||||||
|
return new Measurement(maxWidth, maxWidth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var measurements = _children.Select(c => c.Measure(context, maxWidth));
|
||||||
|
return new Measurement(
|
||||||
|
measurements.Min(c => c.Min),
|
||||||
|
measurements.Min(c => c.Max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
|
||||||
|
{
|
||||||
|
foreach (var child in _children)
|
||||||
|
{
|
||||||
|
var segments = child.Render(context, maxWidth);
|
||||||
|
foreach (var (_, _, last, segment) in segments.Enumerate())
|
||||||
|
{
|
||||||
|
yield return segment;
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
{
|
||||||
|
if (!segment.IsLineBreak)
|
||||||
|
{
|
||||||
|
yield return Segment.LineBreak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user