mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 13:28:16 +08:00

committed by
Patrik Svensson

parent
913a7b1e37
commit
a23bec4082
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class Aligner
|
||||
{
|
||||
|
@ -1,112 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class AnsiBackend : IAnsiConsole
|
||||
{
|
||||
private readonly TextWriter _out;
|
||||
private readonly AnsiBuilder _ansiBuilder;
|
||||
private readonly AnsiCursor _cursor;
|
||||
private readonly ConsoleInput _input;
|
||||
private readonly object _lock;
|
||||
|
||||
public Capabilities Capabilities { get; }
|
||||
public Encoding Encoding { get; }
|
||||
public RenderPipeline Pipeline { get; }
|
||||
public IAnsiConsoleCursor Cursor => _cursor;
|
||||
public IAnsiConsoleInput Input => _input;
|
||||
|
||||
public int Width
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_out.IsStandardOut())
|
||||
{
|
||||
return ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth);
|
||||
}
|
||||
|
||||
return Constants.DefaultTerminalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_out.IsStandardOut())
|
||||
{
|
||||
return ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalHeight);
|
||||
}
|
||||
|
||||
return Constants.DefaultTerminalHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public AnsiBackend(TextWriter @out, Capabilities capabilities, ILinkIdentityGenerator? linkHasher)
|
||||
{
|
||||
_out = @out ?? throw new ArgumentNullException(nameof(@out));
|
||||
|
||||
Capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
|
||||
Encoding = _out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
|
||||
Pipeline = new RenderPipeline();
|
||||
|
||||
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
|
||||
_cursor = new AnsiCursor(this);
|
||||
_input = new ConsoleInput();
|
||||
_lock = new object();
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Write(new[] { Segment.Control("\u001b[2J") });
|
||||
|
||||
if (home)
|
||||
{
|
||||
Cursor.SetPosition(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(IEnumerable<Segment> segments)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
builder.Append(segment.Text);
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = segment.Text.NormalizeNewLines().Split(new[] { '\n' });
|
||||
foreach (var (_, _, last, part) in parts.Enumerate())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
{
|
||||
builder.Append(_ansiBuilder.GetAnsi(part, segment.Style));
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
builder.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
_out.Write(builder.ToString());
|
||||
_out.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class AnsiBuilder
|
||||
{
|
||||
private readonly Capabilities _capabilities;
|
||||
private readonly ILinkIdentityGenerator _linkHasher;
|
||||
private readonly Profile _profile;
|
||||
private readonly AnsiLinkHasher _linkHasher;
|
||||
|
||||
public AnsiBuilder(Capabilities capabilities, ILinkIdentityGenerator? linkHasher)
|
||||
public AnsiBuilder(Profile profile)
|
||||
{
|
||||
_capabilities = capabilities ?? throw new ArgumentNullException(nameof(capabilities));
|
||||
_linkHasher = linkHasher ?? new LinkIdentityGenerator();
|
||||
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
_linkHasher = new AnsiLinkHasher();
|
||||
}
|
||||
|
||||
public string GetAnsi(string text, Style style)
|
||||
@ -28,7 +28,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
codes = codes.Concat(
|
||||
AnsiColorBuilder.GetAnsiCodes(
|
||||
_capabilities.ColorSystem,
|
||||
_profile.ColorSystem,
|
||||
style.Foreground,
|
||||
true));
|
||||
}
|
||||
@ -38,7 +38,7 @@ namespace Spectre.Console.Internal
|
||||
{
|
||||
codes = codes.Concat(
|
||||
AnsiColorBuilder.GetAnsiCodes(
|
||||
_capabilities.ColorSystem,
|
||||
_profile.ColorSystem,
|
||||
style.Background,
|
||||
false));
|
||||
}
|
||||
@ -54,7 +54,7 @@ namespace Spectre.Console.Internal
|
||||
? $"\u001b[{ansiCodes}m{text}\u001b[0m"
|
||||
: text;
|
||||
|
||||
if (style.Link != null && !_capabilities.LegacyConsole)
|
||||
if (style.Link != null && !_profile.Capabilities.Legacy)
|
||||
{
|
||||
var link = style.Link;
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class AnsiColorBuilder
|
||||
{
|
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class AnsiConsoleBackend : IAnsiConsoleBackend
|
||||
{
|
||||
private readonly AnsiBuilder _builder;
|
||||
private readonly Profile _profile;
|
||||
|
||||
public IAnsiConsoleCursor Cursor { get; }
|
||||
|
||||
public AnsiConsoleBackend(Profile profile)
|
||||
{
|
||||
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
_builder = new AnsiBuilder(profile);
|
||||
|
||||
Cursor = new AnsiConsoleCursor(this);
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
Render(new[] { Segment.Control("\u001b[2J") });
|
||||
|
||||
if (home)
|
||||
{
|
||||
Cursor.SetPosition(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(IEnumerable<Segment> segments)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
builder.Append(segment.Text);
|
||||
continue;
|
||||
}
|
||||
|
||||
var parts = segment.Text.NormalizeNewLines().Split(new[] { '\n' });
|
||||
foreach (var (_, _, last, part) in parts.Enumerate())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
{
|
||||
builder.Append(_builder.GetAnsi(part, segment.Style));
|
||||
}
|
||||
|
||||
if (!last)
|
||||
{
|
||||
builder.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.Length > 0)
|
||||
{
|
||||
_profile.Out.Write(builder.ToString());
|
||||
_profile.Out.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class AnsiConsoleCursor : IAnsiConsoleCursor
|
||||
{
|
||||
private readonly AnsiConsoleBackend _backend;
|
||||
|
||||
public AnsiConsoleCursor(AnsiConsoleBackend backend)
|
||||
{
|
||||
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
|
||||
}
|
||||
|
||||
public void Show(bool show)
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
_backend.Render(new[] { Segment.Control("\u001b[?25h") });
|
||||
}
|
||||
else
|
||||
{
|
||||
_backend.Render(new[] { Segment.Control("\u001b[?25l") });
|
||||
}
|
||||
}
|
||||
|
||||
public void Move(CursorDirection direction, int steps)
|
||||
{
|
||||
if (steps == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case CursorDirection.Up:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}A") });
|
||||
break;
|
||||
case CursorDirection.Down:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}B") });
|
||||
break;
|
||||
case CursorDirection.Right:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}C") });
|
||||
break;
|
||||
case CursorDirection.Left:
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{steps}D") });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(int column, int line)
|
||||
{
|
||||
_backend.Render(new[] { Segment.Control($"\u001b[{line};{column}H") });
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class AnsiCursor : IAnsiConsoleCursor
|
||||
{
|
||||
private readonly AnsiBackend _renderer;
|
||||
|
||||
public AnsiCursor(AnsiBackend renderer)
|
||||
{
|
||||
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
public void Show(bool show)
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
_renderer.Write(Segment.Control("\u001b[?25h"));
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.Write(Segment.Control("\u001b[?25l"));
|
||||
}
|
||||
}
|
||||
|
||||
public void Move(CursorDirection direction, int steps)
|
||||
{
|
||||
if (steps == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case CursorDirection.Up:
|
||||
_renderer.Write(Segment.Control($"\u001b[{steps}A"));
|
||||
break;
|
||||
case CursorDirection.Down:
|
||||
_renderer.Write(Segment.Control($"\u001b[{steps}B"));
|
||||
break;
|
||||
case CursorDirection.Right:
|
||||
_renderer.Write(Segment.Control($"\u001b[{steps}C"));
|
||||
break;
|
||||
case CursorDirection.Left:
|
||||
_renderer.Write(Segment.Control($"\u001b[{steps}D"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(int column, int line)
|
||||
{
|
||||
_renderer.Write(Segment.Control($"\u001b[{line};{column}H"));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class AnsiDecorationBuilder
|
||||
{
|
@ -9,7 +9,7 @@ using System.Text.RegularExpressions;
|
||||
// https://github.com/keqingrong/supports-ansi/blob/master/index.js
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class AnsiDetector
|
||||
{
|
||||
@ -34,12 +34,6 @@ namespace Spectre.Console.Internal
|
||||
|
||||
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool upgrade)
|
||||
{
|
||||
// Github action doesn't setup a correct PTY but supports ANSI.
|
||||
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION")))
|
||||
{
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
// Running on Windows?
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
@ -72,7 +66,6 @@ namespace Spectre.Console.Internal
|
||||
return (false, true);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")]
|
||||
internal static class Windows
|
||||
{
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||
@ -96,7 +89,6 @@ namespace Spectre.Console.Internal
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetLastError();
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public static bool SupportsAnsi(bool upgrade, out bool isLegacy)
|
||||
{
|
||||
isLegacy = false;
|
@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class LinkIdentityGenerator : ILinkIdentityGenerator
|
||||
internal sealed class AnsiLinkHasher
|
||||
{
|
||||
private readonly Random _random;
|
||||
|
||||
public LinkIdentityGenerator()
|
||||
public AnsiLinkHasher()
|
||||
{
|
||||
_random = new Random(DateTime.Now.Millisecond);
|
||||
}
|
55
src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
Normal file
55
src/Spectre.Console/Internal/Backends/AnsiConsoleFacade.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class AnsiConsoleFacade : IAnsiConsole
|
||||
{
|
||||
private readonly object _renderLock;
|
||||
private readonly AnsiConsoleBackend _ansiBackend;
|
||||
private readonly LegacyConsoleBackend _legacyBackend;
|
||||
|
||||
public Profile Profile { get; }
|
||||
public IAnsiConsoleCursor Cursor => GetBackend().Cursor;
|
||||
public IAnsiConsoleInput Input { get; }
|
||||
public RenderPipeline Pipeline { get; }
|
||||
|
||||
public AnsiConsoleFacade(Profile profile)
|
||||
{
|
||||
_renderLock = new object();
|
||||
_ansiBackend = new AnsiConsoleBackend(profile);
|
||||
_legacyBackend = new LegacyConsoleBackend(profile);
|
||||
|
||||
Profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
Input = new DefaultInput(Profile);
|
||||
Pipeline = new RenderPipeline();
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
lock (_renderLock)
|
||||
{
|
||||
GetBackend().Clear(home);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(IEnumerable<Segment> segments)
|
||||
{
|
||||
lock (_renderLock)
|
||||
{
|
||||
GetBackend().Render(segments);
|
||||
}
|
||||
}
|
||||
|
||||
private IAnsiConsoleBackend GetBackend()
|
||||
{
|
||||
if (Profile.Capabilities.Ansi)
|
||||
{
|
||||
return _ansiBackend;
|
||||
}
|
||||
|
||||
return _legacyBackend;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class BackendBuilder
|
||||
{
|
||||
public static IAnsiConsole Build(AnsiConsoleSettings settings)
|
||||
{
|
||||
if (settings is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
var buffer = settings.Out ?? System.Console.Out;
|
||||
|
||||
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
|
||||
var legacyConsole = false;
|
||||
|
||||
if (settings.Ansi == AnsiSupport.Detect)
|
||||
{
|
||||
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(true);
|
||||
|
||||
// Check whether or not this is a legacy console from the existing instance (if any).
|
||||
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
|
||||
if (AnsiConsole.Created && !legacyConsole && buffer.IsStandardOut() && AnsiConsole.Capabilities.LegacyConsole)
|
||||
{
|
||||
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffer.IsStandardOut())
|
||||
{
|
||||
// Are we running on Windows?
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Not the first console we're creating?
|
||||
if (AnsiConsole.Created)
|
||||
{
|
||||
legacyConsole = AnsiConsole.Capabilities.LegacyConsole;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try detecting whether or not this
|
||||
(_, legacyConsole) = AnsiDetector.Detect(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var supportsInteraction = settings.Interactive == InteractionSupport.Yes;
|
||||
if (settings.Interactive == InteractionSupport.Detect)
|
||||
{
|
||||
supportsInteraction = InteractivityDetector.IsInteractive();
|
||||
}
|
||||
|
||||
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
|
||||
? ColorSystemDetector.Detect(supportsAnsi)
|
||||
: (ColorSystem)settings.ColorSystem;
|
||||
|
||||
// Get the capabilities
|
||||
var capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole, supportsInteraction);
|
||||
|
||||
// Create the renderer
|
||||
if (supportsAnsi)
|
||||
{
|
||||
return new AnsiBackend(buffer, capabilities, settings.LinkIdentityGenerator);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FallbackBackend(buffer, capabilities);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class FallbackBackend : IAnsiConsole
|
||||
{
|
||||
private readonly ColorSystem _system;
|
||||
private readonly FallbackCursor _cursor;
|
||||
private readonly ConsoleInput _input;
|
||||
private Style? _lastStyle;
|
||||
|
||||
public Capabilities Capabilities { get; }
|
||||
public Encoding Encoding { get; }
|
||||
public RenderPipeline Pipeline { get; }
|
||||
public IAnsiConsoleCursor Cursor => _cursor;
|
||||
public IAnsiConsoleInput Input => _input;
|
||||
|
||||
public int Width
|
||||
{
|
||||
get { return ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth); }
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
get { return ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalHeight); }
|
||||
}
|
||||
|
||||
public FallbackBackend(TextWriter @out, Capabilities capabilities)
|
||||
{
|
||||
if (capabilities == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(capabilities));
|
||||
}
|
||||
|
||||
_system = capabilities.ColorSystem;
|
||||
_cursor = new FallbackCursor();
|
||||
_input = new ConsoleInput();
|
||||
|
||||
if (@out != System.Console.Out)
|
||||
{
|
||||
System.Console.SetOut(@out ?? throw new ArgumentNullException(nameof(@out)));
|
||||
}
|
||||
|
||||
Capabilities = capabilities;
|
||||
Encoding = System.Console.OutputEncoding;
|
||||
Pipeline = new RenderPipeline();
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
var (x, y) = (System.Console.CursorLeft, System.Console.CursorTop);
|
||||
|
||||
System.Console.Clear();
|
||||
|
||||
if (!home)
|
||||
{
|
||||
// Set the cursor position
|
||||
System.Console.SetCursorPosition(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(IEnumerable<Segment> segments)
|
||||
{
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_lastStyle?.Equals(segment.Style) != true)
|
||||
{
|
||||
SetStyle(segment.Style);
|
||||
}
|
||||
|
||||
System.Console.Write(segment.Text.NormalizeNewLines(native: true));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStyle(Style style)
|
||||
{
|
||||
_lastStyle = style;
|
||||
|
||||
System.Console.ResetColor();
|
||||
|
||||
var background = Color.ToConsoleColor(style.Background);
|
||||
if (_system != ColorSystem.NoColors && (int)background != -1)
|
||||
{
|
||||
System.Console.BackgroundColor = background;
|
||||
}
|
||||
|
||||
var foreground = Color.ToConsoleColor(style.Foreground);
|
||||
if (_system != ColorSystem.NoColors && (int)foreground != -1)
|
||||
{
|
||||
System.Console.ForegroundColor = foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
src/Spectre.Console/Internal/Backends/IAnsiConsoleBackend.cs
Normal file
28
src/Spectre.Console/Internal/Backends/IAnsiConsoleBackend.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a console backend.
|
||||
/// </summary>
|
||||
internal interface IAnsiConsoleBackend
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the console cursor for the backend.
|
||||
/// </summary>
|
||||
IAnsiConsoleCursor Cursor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears the console.
|
||||
/// </summary>
|
||||
/// <param name="home">If the cursor should be moved to the home position.</param>
|
||||
void Clear(bool home);
|
||||
|
||||
/// <summary>
|
||||
/// Renders segments to the console.
|
||||
/// </summary>
|
||||
/// <param name="segments">The segments to render.</param>
|
||||
void Render(IEnumerable<Segment> segments);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class InteractivityDetector
|
||||
{
|
@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class LegacyConsoleBackend : IAnsiConsoleBackend
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
private Style _lastStyle;
|
||||
|
||||
public IAnsiConsoleCursor Cursor { get; }
|
||||
|
||||
public LegacyConsoleBackend(Profile profile)
|
||||
{
|
||||
_profile = profile ?? throw new System.ArgumentNullException(nameof(profile));
|
||||
_lastStyle = Style.Plain;
|
||||
|
||||
Cursor = new LegacyConsoleCursor();
|
||||
}
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
var (x, y) = (System.Console.CursorLeft, System.Console.CursorTop);
|
||||
|
||||
System.Console.Clear();
|
||||
|
||||
if (!home)
|
||||
{
|
||||
// Set the cursor position
|
||||
System.Console.SetCursorPosition(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(IEnumerable<Segment> segments)
|
||||
{
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (segment.IsControlCode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_lastStyle?.Equals(segment.Style) != true)
|
||||
{
|
||||
SetStyle(segment.Style);
|
||||
}
|
||||
|
||||
_profile.Out.Write(segment.Text.NormalizeNewLines(native: true));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStyle(Style style)
|
||||
{
|
||||
_lastStyle = style;
|
||||
|
||||
System.Console.ResetColor();
|
||||
|
||||
var background = Color.ToConsoleColor(style.Background);
|
||||
if (_profile.ColorSystem != ColorSystem.NoColors && (int)background != -1)
|
||||
{
|
||||
System.Console.BackgroundColor = background;
|
||||
}
|
||||
|
||||
var foreground = Color.ToConsoleColor(style.Foreground);
|
||||
if (_profile.ColorSystem != ColorSystem.NoColors && (int)foreground != -1)
|
||||
{
|
||||
System.Console.ForegroundColor = foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class FallbackCursor : IAnsiConsoleCursor
|
||||
internal sealed class LegacyConsoleCursor : IAnsiConsoleCursor
|
||||
{
|
||||
public void Show(bool show)
|
||||
{
|
@ -2,7 +2,7 @@ using System.Linq;
|
||||
using Spectre.Console.Rendering;
|
||||
using Wcwidth;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class Cell
|
||||
{
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class ListWithCallback<T> : IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static partial class ColorPalette
|
||||
{
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class ColorSystemDetector
|
||||
{
|
||||
|
@ -11,7 +11,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
|
@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static partial class ColorTable
|
||||
{
|
||||
private static readonly Dictionary<int, string> _nameLookup;
|
||||
private static readonly Dictionary<string, int> _numberLookup;
|
||||
|
||||
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
|
||||
static ColorTable()
|
||||
{
|
||||
_numberLookup = GenerateTable();
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class ConsoleHelper
|
||||
{
|
||||
|
@ -1,17 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class ConsoleInput : IAnsiConsoleInput
|
||||
{
|
||||
public ConsoleKeyInfo ReadKey(bool intercept)
|
||||
{
|
||||
if (!Environment.UserInteractive)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
|
||||
}
|
||||
|
||||
return System.Console.ReadKey(intercept);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
|
@ -1,16 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
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()
|
||||
{
|
||||
_lookup = new Dictionary<string, Decoration?>(StringComparer.OrdinalIgnoreCase)
|
||||
|
29
src/Spectre.Console/Internal/DefaultInput.cs
Normal file
29
src/Spectre.Console/Internal/DefaultInput.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class DefaultInput : IAnsiConsoleInput
|
||||
{
|
||||
private readonly Profile _profile;
|
||||
|
||||
public DefaultInput(Profile profile)
|
||||
{
|
||||
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
|
||||
}
|
||||
|
||||
public ConsoleKeyInfo ReadKey(bool intercept)
|
||||
{
|
||||
if (_profile.Capabilities.Tty)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot read input from a TTY console.");
|
||||
}
|
||||
|
||||
if (!_profile.Capabilities.Interactive)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
|
||||
}
|
||||
|
||||
return System.Console.ReadKey(intercept);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
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.EscapeMarkup()}[/]";
|
||||
return new Markup(string.Concat(type, ": ", message));
|
||||
}
|
||||
|
||||
private static Grid GetStackFrames(ExceptionInfo ex, ExceptionSettings settings)
|
||||
{
|
||||
var styles = settings.Style;
|
||||
|
||||
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[] { '.' }, styles.Method, shortenMethods, settings));
|
||||
builder.AppendWithStyle(styles.Parenthesis, "(");
|
||||
AppendParameters(builder, frame, settings);
|
||||
builder.AppendWithStyle(styles.Parenthesis, ")");
|
||||
|
||||
if (frame.Path != null)
|
||||
{
|
||||
builder.Append(' ');
|
||||
builder.AppendWithStyle(styles.Dimmed, "in");
|
||||
builder.Append(' ');
|
||||
|
||||
// Path
|
||||
AppendPath(builder, frame, settings);
|
||||
|
||||
// Line number
|
||||
if (frame.LineNumber != null)
|
||||
{
|
||||
builder.AppendWithStyle(styles.Dimmed, ":");
|
||||
builder.AppendWithStyle(styles.LineNumber, frame.LineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
grid.AddRow(
|
||||
$"[{styles.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.EscapeMarkup()}[/] [{nameColor}]{x.Name.EscapeMarkup()}[/]");
|
||||
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.AppendWithStyle(
|
||||
settings.Style.NonEmphasized,
|
||||
type.Substring(0, index + 1).EscapeMarkup());
|
||||
}
|
||||
|
||||
builder.AppendWithStyle(
|
||||
color,
|
||||
type.Substring(index + 1, type.Length - index - 1).EscapeMarkup());
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(type.EscapeMarkup());
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
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.ReplaceExact(" ---> ", 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal struct FileSize
|
||||
{
|
||||
@ -31,7 +31,7 @@ namespace Spectre.Console.Internal
|
||||
|
||||
var bytes = Bytes / @base;
|
||||
|
||||
return Unit == FileSizeUnit.Bytes
|
||||
return Unit == FileSizeUnit.Byte
|
||||
? ((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
|
||||
: bytes.ToString("F1", culture ?? CultureInfo.InvariantCulture);
|
||||
}
|
||||
@ -78,7 +78,7 @@ namespace Spectre.Console.Internal
|
||||
}
|
||||
}
|
||||
|
||||
return FileSizeUnit.Bytes;
|
||||
return FileSizeUnit.Byte;
|
||||
}
|
||||
|
||||
private static double GetBase(FileSizeUnit unit)
|
||||
|
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal enum FileSizeUnit
|
||||
{
|
||||
Bytes = 0,
|
||||
Byte = 0,
|
||||
KiloByte = 1,
|
||||
MegaByte = 2,
|
||||
GigaByte = 3,
|
||||
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class Ratio
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class ResourceReader
|
||||
{
|
||||
|
@ -1,65 +0,0 @@
|
||||
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 ArgumentNullException(nameof(method));
|
||||
Parameters = parameters ?? throw new 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class HtmlEncoder : IAnsiConsoleEncoder
|
||||
{
|
@ -2,7 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class TextEncoder : IAnsiConsoleEncoder
|
||||
{
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class MarkupParser
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class MarkupToken
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal enum MarkupTokenKind
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class MarkupTokenizer : IDisposable
|
||||
{
|
||||
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal sealed class StringBuffer : IDisposable
|
||||
{
|
||||
[SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")]
|
||||
private readonly StringReader _reader;
|
||||
private readonly int _length;
|
||||
|
||||
|
@ -1,218 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class StyleParser
|
||||
{
|
||||
public static Style Parse(string text)
|
||||
{
|
||||
var style = Parse(text, out var error);
|
||||
if (error != null)
|
||||
{
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
|
||||
if (style == null)
|
||||
{
|
||||
// This should not happen, but we need to please the compiler
|
||||
// which cannot know that style isn't null here.
|
||||
throw new InvalidOperationException("Could not parse style.");
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
public static bool TryParse(string text, out Style? style)
|
||||
{
|
||||
style = Parse(text, out var error);
|
||||
return error == null;
|
||||
}
|
||||
|
||||
private static Style? Parse(string text, out string? error)
|
||||
{
|
||||
var effectiveDecoration = (Decoration?)null;
|
||||
var effectiveForeground = (Color?)null;
|
||||
var effectiveBackground = (Color?)null;
|
||||
var effectiveLink = (string?)null;
|
||||
|
||||
var parts = text.Split(new[] { ' ' });
|
||||
var foreground = true;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (part.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreground = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.StartsWith("link=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (effectiveLink != null)
|
||||
{
|
||||
error = "A link has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveLink = part.Substring(5);
|
||||
continue;
|
||||
}
|
||||
else if (part.StartsWith("link", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
effectiveLink = Constants.EmptyLink;
|
||||
continue;
|
||||
}
|
||||
|
||||
var decoration = DecorationTable.GetDecoration(part);
|
||||
if (decoration != null)
|
||||
{
|
||||
effectiveDecoration ??= Decoration.None;
|
||||
|
||||
effectiveDecoration |= decoration.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var color = ColorTable.GetColor(part);
|
||||
if (color == null)
|
||||
{
|
||||
if (part.StartsWith("#", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
color = ParseHexColor(part, out error);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
color = ParseRgbColor(part, out error);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error = !foreground
|
||||
? $"Could not find color '{part}'."
|
||||
: $"Could not find color or style '{part}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (foreground)
|
||||
{
|
||||
if (effectiveForeground != null)
|
||||
{
|
||||
error = "A foreground color has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveForeground = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectiveBackground != null)
|
||||
{
|
||||
error = "A background color has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveBackground = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error = null;
|
||||
return new Style(
|
||||
effectiveForeground,
|
||||
effectiveBackground,
|
||||
effectiveDecoration,
|
||||
effectiveLink);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
private static Color? ParseHexColor(string hex, out string? error)
|
||||
{
|
||||
error = null;
|
||||
|
||||
hex ??= string.Empty;
|
||||
hex = hex.ReplaceExact("#", string.Empty).Trim();
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(hex))
|
||||
{
|
||||
if (hex.Length == 6)
|
||||
{
|
||||
return new Color(
|
||||
(byte)Convert.ToUInt32(hex.Substring(0, 2), 16),
|
||||
(byte)Convert.ToUInt32(hex.Substring(2, 2), 16),
|
||||
(byte)Convert.ToUInt32(hex.Substring(4, 2), 16));
|
||||
}
|
||||
else if (hex.Length == 3)
|
||||
{
|
||||
return new Color(
|
||||
(byte)Convert.ToUInt32(new string(hex[0], 2), 16),
|
||||
(byte)Convert.ToUInt32(new string(hex[1], 2), 16),
|
||||
(byte)Convert.ToUInt32(new string(hex[2], 2), 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Invalid hex color '#{hex}'. {ex.Message}";
|
||||
return null;
|
||||
}
|
||||
|
||||
error = $"Invalid hex color '#{hex}'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
private static Color? ParseRgbColor(string rgb, out string? error)
|
||||
{
|
||||
try
|
||||
{
|
||||
error = null;
|
||||
|
||||
var normalized = rgb ?? string.Empty;
|
||||
if (normalized.Length >= 3)
|
||||
{
|
||||
// Trim parentheses
|
||||
normalized = normalized.Substring(3).Trim();
|
||||
|
||||
if (normalized.StartsWith("(", StringComparison.OrdinalIgnoreCase) &&
|
||||
normalized.EndsWith(")", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
normalized = normalized.Trim('(').Trim(')');
|
||||
|
||||
var parts = normalized.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
return new Color(
|
||||
(byte)Convert.ToInt32(parts[0], CultureInfo.InvariantCulture),
|
||||
(byte)Convert.ToInt32(parts[1], CultureInfo.InvariantCulture),
|
||||
(byte)Convert.ToInt32(parts[2], CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Invalid RGB color '{rgb}'. {ex.Message}";
|
||||
return null;
|
||||
}
|
||||
|
||||
error = $"Invalid RGB color '{rgb}'.";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class TypeConverterHelper
|
||||
{
|
||||
@ -12,7 +12,6 @@ namespace Spectre.Console.Internal
|
||||
return GetTypeConverter<T>().ConvertToInvariantString(input);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public static bool TryConvertFromString<T>(string input, [MaybeNull] out T result)
|
||||
{
|
||||
try
|
||||
|
Reference in New Issue
Block a user