mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 10:35:27 +08:00 
			
		
		
		
	
				
					committed by
					
						
						Patrik Svensson
					
				
			
			
				
	
			
			
			
						parent
						
							913a7b1e37
						
					
				
				
					commit
					a23bec4082
				
			@@ -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();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										74
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiBuilder.cs
									
									
									
									
									
										Normal 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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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 };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										138
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiDetector.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiDetector.cs
									
									
									
									
									
										Normal 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;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiLinkHasher.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiLinkHasher.cs
									
									
									
									
									
										Normal 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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
        {
 | 
			
		||||
		Reference in New Issue
	
	Block a user