mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 10:35:27 +08:00 
			
		
		
		
	Add support for moving the cursor
This commit is contained in:
		
				
					committed by
					
						
						Patrik Svensson
					
				
			
			
				
	
			
			
			
						parent
						
							93d1971f48
						
					
				
				
					commit
					a1d11e9d0c
				
			
							
								
								
									
										82
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiBackend.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiBackend.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
using System;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
        public Capabilities Capabilities { get; }
 | 
			
		||||
        public Encoding Encoding { get; }
 | 
			
		||||
        public IAnsiConsoleCursor Cursor => _cursor;
 | 
			
		||||
 | 
			
		||||
        public int Width
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (_out.IsStandardOut())
 | 
			
		||||
                {
 | 
			
		||||
                    return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Constants.DefaultBufferWidth;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int Height
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                if (_out.IsStandardOut())
 | 
			
		||||
                {
 | 
			
		||||
                    return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Constants.DefaultBufferHeight;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
            _ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
 | 
			
		||||
            _cursor = new AnsiCursor(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Clear(bool home)
 | 
			
		||||
        {
 | 
			
		||||
            Write(Segment.Control("\u001b[2J"));
 | 
			
		||||
 | 
			
		||||
            if (home)
 | 
			
		||||
            {
 | 
			
		||||
                Cursor.SetPosition(0, 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Write(Segment segment)
 | 
			
		||||
        {
 | 
			
		||||
            var parts = segment.Text.NormalizeLineEndings().Split(new[] { '\n' });
 | 
			
		||||
            foreach (var (_, _, last, part) in parts.Enumerate())
 | 
			
		||||
            {
 | 
			
		||||
                if (!string.IsNullOrEmpty(part))
 | 
			
		||||
                {
 | 
			
		||||
                    _out.Write(_ansiBuilder.GetAnsi(part, segment.Style));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!last)
 | 
			
		||||
                {
 | 
			
		||||
                    _out.Write(Environment.NewLine);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiCursor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/Spectre.Console/Internal/Backends/Ansi/AnsiCursor.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
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"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								src/Spectre.Console/Internal/Backends/BackendBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/Spectre.Console/Internal/Backends/BackendBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
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 colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
 | 
			
		||||
                ? ColorSystemDetector.Detect(supportsAnsi)
 | 
			
		||||
                : (ColorSystem)settings.ColorSystem;
 | 
			
		||||
 | 
			
		||||
            // Get the capabilities
 | 
			
		||||
            var capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole);
 | 
			
		||||
 | 
			
		||||
            // Create the renderer
 | 
			
		||||
            if (supportsAnsi)
 | 
			
		||||
            {
 | 
			
		||||
                return new AnsiBackend(buffer, capabilities, settings.LinkIdentityGenerator);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return new FallbackBackend(buffer, capabilities);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
using System;
 | 
			
		||||
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 Style? _lastStyle;
 | 
			
		||||
 | 
			
		||||
        public Capabilities Capabilities { get; }
 | 
			
		||||
        public Encoding Encoding { get; }
 | 
			
		||||
        public IAnsiConsoleCursor Cursor => _cursor;
 | 
			
		||||
 | 
			
		||||
        public int Width
 | 
			
		||||
        {
 | 
			
		||||
            get { return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth); }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int Height
 | 
			
		||||
        {
 | 
			
		||||
            get { return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight); }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public FallbackBackend(TextWriter @out, Capabilities capabilities)
 | 
			
		||||
        {
 | 
			
		||||
            if (capabilities == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(capabilities));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _system = capabilities.ColorSystem;
 | 
			
		||||
            _cursor = new FallbackCursor();
 | 
			
		||||
 | 
			
		||||
            if (@out != System.Console.Out)
 | 
			
		||||
            {
 | 
			
		||||
                System.Console.SetOut(@out ?? throw new ArgumentNullException(nameof(@out)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Encoding = System.Console.OutputEncoding;
 | 
			
		||||
            Capabilities = capabilities;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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(Segment segment)
 | 
			
		||||
        {
 | 
			
		||||
            if (_lastStyle?.Equals(segment.Style) != true)
 | 
			
		||||
            {
 | 
			
		||||
                SetStyle(segment.Style);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            System.Console.Write(segment.Text.NormalizeLineEndings(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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
namespace Spectre.Console.Internal
 | 
			
		||||
{
 | 
			
		||||
    internal sealed class FallbackCursor : IAnsiConsoleCursor
 | 
			
		||||
    {
 | 
			
		||||
        public void Show(bool show)
 | 
			
		||||
        {
 | 
			
		||||
            System.Console.CursorVisible = show;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Move(CursorDirection direction, int steps)
 | 
			
		||||
        {
 | 
			
		||||
            if (steps == 0)
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch (direction)
 | 
			
		||||
            {
 | 
			
		||||
                case CursorDirection.Up:
 | 
			
		||||
                    System.Console.CursorTop -= steps;
 | 
			
		||||
                    break;
 | 
			
		||||
                case CursorDirection.Down:
 | 
			
		||||
                    System.Console.CursorTop += steps;
 | 
			
		||||
                    break;
 | 
			
		||||
                case CursorDirection.Left:
 | 
			
		||||
                    System.Console.CursorLeft -= steps;
 | 
			
		||||
                    break;
 | 
			
		||||
                case CursorDirection.Right:
 | 
			
		||||
                    System.Console.CursorLeft += steps;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void SetPosition(int x, int y)
 | 
			
		||||
        {
 | 
			
		||||
            System.Console.CursorLeft = x;
 | 
			
		||||
            System.Console.CursorTop = y;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user