Add profile support

Closes #231
This commit is contained in:
Patrik Svensson
2021-01-16 17:23:58 +01:00
committed by Patrik Svensson
parent 913a7b1e37
commit a23bec4082
230 changed files with 1241 additions and 1628 deletions

View File

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

View File

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

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Spectre.Console
{
internal static class AnsiColorBuilder
{
public static IEnumerable<byte> GetAnsiCodes(ColorSystem system, Color color, bool foreground)
{
return system switch
{
ColorSystem.NoColors => Array.Empty<byte>(), // No colors
ColorSystem.TrueColor => GetTrueColor(color, foreground), // 24-bit
ColorSystem.EightBit => GetEightBit(color, foreground), // 8-bit
ColorSystem.Standard => GetFourBit(color, foreground), // 4-bit
ColorSystem.Legacy => GetThreeBit(color, foreground), // 3-bit
_ => throw new InvalidOperationException("Could not determine ANSI color."),
};
}
private static IEnumerable<byte> GetThreeBit(Color color, bool foreground)
{
var number = color.Number;
if (number == null || color.Number >= 8)
{
number = ColorPalette.ExactOrClosest(ColorSystem.Legacy, color).Number;
}
Debug.Assert(number >= 0 && number < 8, "Invalid range for 4-bit color");
var mod = foreground ? 30 : 40;
return new byte[] { (byte)(number.Value + mod) };
}
private static IEnumerable<byte> GetFourBit(Color color, bool foreground)
{
var number = color.Number;
if (number == null || color.Number >= 16)
{
number = ColorPalette.ExactOrClosest(ColorSystem.Standard, color).Number;
}
Debug.Assert(number >= 0 && number < 16, "Invalid range for 4-bit color");
var mod = number < 8 ? (foreground ? 30 : 40) : (foreground ? 82 : 92);
return new byte[] { (byte)(number.Value + mod) };
}
private static IEnumerable<byte> GetEightBit(Color color, bool foreground)
{
var number = color.Number ?? ColorPalette.ExactOrClosest(ColorSystem.EightBit, color).Number;
Debug.Assert(number >= 0 && number <= 255, "Invalid range for 8-bit color");
var mod = foreground ? (byte)38 : (byte)48;
return new byte[] { mod, 5, (byte)number };
}
private static IEnumerable<byte> GetTrueColor(Color color, bool foreground)
{
if (color.Number != null)
{
return GetEightBit(color, foreground);
}
var mod = foreground ? (byte)38 : (byte)48;
return new byte[] { mod, 2, color.R, color.G, color.B };
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
namespace Spectre.Console
{
internal static class AnsiDecorationBuilder
{
// TODO: Rewrite this to not yield
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
{
if ((decoration & Decoration.Bold) != 0)
{
yield return 1;
}
if ((decoration & Decoration.Dim) != 0)
{
yield return 2;
}
if ((decoration & Decoration.Italic) != 0)
{
yield return 3;
}
if ((decoration & Decoration.Underline) != 0)
{
yield return 4;
}
if ((decoration & Decoration.SlowBlink) != 0)
{
yield return 5;
}
if ((decoration & Decoration.RapidBlink) != 0)
{
yield return 6;
}
if ((decoration & Decoration.Invert) != 0)
{
yield return 7;
}
if ((decoration & Decoration.Conceal) != 0)
{
yield return 8;
}
if ((decoration & Decoration.Strikethrough) != 0)
{
yield return 9;
}
}
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Portions of this code was ported from the supports-ansi project by Qingrong Ke
// https://github.com/keqingrong/supports-ansi/blob/master/index.js
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Spectre.Console
{
internal static class AnsiDetector
{
private static readonly Regex[] _regexes = new[]
{
new Regex("^xterm"), // xterm, PuTTY, Mintty
new Regex("^rxvt"), // RXVT
new Regex("^eterm"), // Eterm
new Regex("^screen"), // GNU screen, tmux
new Regex("tmux"), // tmux
new Regex("^vt100"), // DEC VT series
new Regex("^vt102"), // DEC VT series
new Regex("^vt220"), // DEC VT series
new Regex("^vt320"), // DEC VT series
new Regex("ansi"), // ANSI
new Regex("scoansi"), // SCO ANSI
new Regex("cygwin"), // Cygwin, MinGW
new Regex("linux"), // Linux console
new Regex("konsole"), // Konsole
new Regex("bvterm"), // Bitvise SSH Client
};
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool upgrade)
{
// Running on Windows?
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Running under ConEmu?
var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI");
if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase))
{
return (true, false);
}
var supportsAnsi = Windows.SupportsAnsi(upgrade, out var legacyConsole);
return (supportsAnsi, legacyConsole);
}
return DetectFromTerm();
}
private static (bool SupportsAnsi, bool LegacyConsole) DetectFromTerm()
{
// Check if the terminal is of type ANSI/VT100/xterm compatible.
var term = Environment.GetEnvironmentVariable("TERM");
if (!string.IsNullOrWhiteSpace(term))
{
if (_regexes.Any(regex => regex.IsMatch(term)))
{
return (true, false);
}
}
return (false, true);
}
internal static class Windows
{
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const int STD_OUTPUT_HANDLE = -11;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
[DllImport("kernel32.dll")]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
public static bool SupportsAnsi(bool upgrade, out bool isLegacy)
{
isLegacy = false;
try
{
var @out = GetStdHandle(STD_OUTPUT_HANDLE);
if (!GetConsoleMode(@out, out var mode))
{
// Could not get console mode, try TERM (set in cygwin, WSL-Shell).
var (ansiFromTerm, legacyFromTerm) = DetectFromTerm();
isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy;
return ansiFromTerm;
}
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
{
isLegacy = true;
if (!upgrade)
{
return false;
}
// Try enable ANSI support.
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
if (!SetConsoleMode(@out, mode))
{
// Enabling failed.
return false;
}
isLegacy = false;
}
return true;
}
catch
{
// All we know here is that we don't support ANSI.
return false;
}
}
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Runtime.CompilerServices;
namespace Spectre.Console
{
internal sealed class AnsiLinkHasher
{
private readonly Random _random;
public AnsiLinkHasher()
{
_random = new Random(DateTime.Now.Millisecond);
}
public int GenerateId(string link, string text)
{
if (link is null)
{
throw new ArgumentNullException(nameof(link));
}
link += text ?? string.Empty;
unchecked
{
return Math.Abs(
GetLinkHashCode(link) +
_random.Next(0, int.MaxValue));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLinkHashCode(string link)
{
#if NET5_0
return link.GetHashCode(StringComparison.Ordinal);
#else
return link.GetHashCode();
#endif
}
}
}

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

View File

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

View File

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

View 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);
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console
{
internal static class InteractivityDetector
{
private static readonly Dictionary<string, Func<string, bool>> _environmentVariables;
static InteractivityDetector()
{
_environmentVariables = new Dictionary<string, Func<string, bool>>
{
{ "APPVEYOR", v => !string.IsNullOrWhiteSpace(v) },
{ "bamboo_buildNumber", v => !string.IsNullOrWhiteSpace(v) },
{ "BITBUCKET_REPO_OWNER", v => !string.IsNullOrWhiteSpace(v) },
{ "BITBUCKET_REPO_SLUG", v => !string.IsNullOrWhiteSpace(v) },
{ "BITBUCKET_COMMIT", v => !string.IsNullOrWhiteSpace(v) },
{ "BITRISE_BUILD_URL", v => !string.IsNullOrWhiteSpace(v) },
{ "ContinuaCI.Version", v => !string.IsNullOrWhiteSpace(v) },
{ "CI_SERVER", v => v.Equals("yes", StringComparison.OrdinalIgnoreCase) }, // GitLab
{ "GITHUB_ACTIONS", v => v.Equals("true", StringComparison.OrdinalIgnoreCase) },
{ "GO_SERVER_URL", v => !string.IsNullOrWhiteSpace(v) },
{ "JENKINS_URL", v => !string.IsNullOrWhiteSpace(v) },
{ "BuildRunner", v => v.Equals("MyGet", StringComparison.OrdinalIgnoreCase) },
{ "TEAMCITY_VERSION", v => !string.IsNullOrWhiteSpace(v) },
{ "TF_BUILD", v => !string.IsNullOrWhiteSpace(v) }, // TFS and Azure
{ "TRAVIS", v => !string.IsNullOrWhiteSpace(v) },
};
}
public static bool IsInteractive()
{
if (!Environment.UserInteractive)
{
return false;
}
foreach (var variable in _environmentVariables)
{
var func = variable.Value;
var value = Environment.GetEnvironmentVariable(variable.Key);
if (!string.IsNullOrWhiteSpace(value) && variable.Value(value))
{
return false;
}
}
return true;
}
}
}

View File

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

View File

@ -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)
{