mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 00:42:51 +08:00
Make VT-100 sequences easier to understand
This commit is contained in:
parent
20650f1e7e
commit
1ed7e65fcb
@ -0,0 +1,10 @@
|
||||
[?25l
|
||||
[38;5;11m*[0m foo
|
||||
|
||||
[2A
|
||||
[38;5;11m-[0m bar
|
||||
|
||||
[2A
|
||||
[38;5;11m*[0m baz
|
||||
|
||||
[2K[1A[2K[1A[2K[?25h
|
52
src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Cursor.cs
Normal file
52
src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Cursor.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Shouldly;
|
||||
using Spectre.Console.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
public sealed class Cursor
|
||||
{
|
||||
public sealed class TheMoveMethod
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(CursorDirection.Up, "Hello[2AWorld")]
|
||||
[InlineData(CursorDirection.Down, "Hello[2BWorld")]
|
||||
[InlineData(CursorDirection.Right, "Hello[2CWorld")]
|
||||
[InlineData(CursorDirection.Left, "Hello[2DWorld")]
|
||||
public void Should_Return_Correct_Ansi_Code(CursorDirection direction, string expected)
|
||||
{
|
||||
// Given
|
||||
var console = new FakeAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
console.Write("Hello");
|
||||
console.Cursor.Move(direction, 2);
|
||||
console.Write("World");
|
||||
|
||||
// Then
|
||||
console.Output.ShouldBe(expected);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TheSetPositionMethod
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Return_Correct_Ansi_Code()
|
||||
{
|
||||
// Given
|
||||
var console = new FakeAnsiConsole(ColorSystem.Standard, AnsiSupport.Yes);
|
||||
|
||||
// When
|
||||
console.Write("Hello");
|
||||
console.Cursor.SetPosition(5, 3);
|
||||
console.Write("World");
|
||||
|
||||
// Then
|
||||
console.Output.ShouldBe("Hello[3;5HWorld");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,23 @@ namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
public partial class AnsiConsoleTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(false, "Hello[2JWorld")]
|
||||
[InlineData(true, "Hello[2J[0;0HWorld")]
|
||||
public void Should_Clear_Screen(bool home, string expected)
|
||||
{
|
||||
// Given
|
||||
var console = new FakeAnsiConsole(ColorSystem.Standard);
|
||||
|
||||
// When
|
||||
console.Write("Hello");
|
||||
console.Clear(home);
|
||||
console.Write("World");
|
||||
|
||||
// Then
|
||||
console.Output.ShouldBe(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Combine_Decoration_And_Colors()
|
||||
{
|
||||
|
@ -1,13 +1,18 @@
|
||||
using Shouldly;
|
||||
using System.Threading.Tasks;
|
||||
using Spectre.Console.Testing;
|
||||
using Spectre.Verify.Extensions;
|
||||
using VerifyXunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit
|
||||
{
|
||||
[UsesVerify]
|
||||
[ExpectationPath("Widgets/Status")]
|
||||
public sealed class StatusTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Render_Status_Correctly()
|
||||
[Expectation("Render")]
|
||||
public Task Should_Render_Status_Correctly()
|
||||
{
|
||||
// Given
|
||||
var console = new FakeAnsiConsole(ColorSystem.TrueColor, width: 10);
|
||||
@ -28,16 +33,7 @@ namespace Spectre.Console.Tests.Unit
|
||||
});
|
||||
|
||||
// Then
|
||||
console.Output
|
||||
.NormalizeLineEndings()
|
||||
.ShouldBe(
|
||||
"[?25l \n" +
|
||||
"[38;5;11m*[0m foo\n" +
|
||||
" [1A[1A \n" +
|
||||
"[38;5;11m-[0m bar\n" +
|
||||
" [1A[1A \n" +
|
||||
"[38;5;11m*[0m baz\n" +
|
||||
" [2K[1A[2K[1A[2K[?25h");
|
||||
return Verifier.Verify(console.Output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using static Spectre.Console.AnsiSequences;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@ -49,9 +50,8 @@ namespace Spectre.Console
|
||||
return text;
|
||||
}
|
||||
|
||||
var ansiCodes = string.Join(";", result);
|
||||
var ansi = result.Length > 0
|
||||
? $"\u001b[{ansiCodes}m{text}\u001b[0m"
|
||||
? $"{SGR(result)}{text}{SGR(0)}"
|
||||
: text;
|
||||
|
||||
if (style.Link != null && !_profile.Capabilities.Legacy)
|
||||
@ -65,7 +65,7 @@ namespace Spectre.Console
|
||||
}
|
||||
|
||||
var linkId = _linkHasher.GenerateId(link, text);
|
||||
ansi = $"\u001b]8;id={linkId};{link}\u001b\\{ansi}\u001b]8;;\u001b\\";
|
||||
ansi = $"{CSI}]8;id={linkId};{link}{CSI}\\{ansi}{CSI}]8;;{CSI}\\";
|
||||
}
|
||||
|
||||
return ansi;
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Spectre.Console.Rendering;
|
||||
using static Spectre.Console.AnsiSequences;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@ -21,11 +22,11 @@ namespace Spectre.Console
|
||||
|
||||
public void Clear(bool home)
|
||||
{
|
||||
Write(new ControlSequence("\u001b[2J"));
|
||||
Write(new ControlSequence(ED(2)));
|
||||
|
||||
if (home)
|
||||
{
|
||||
Cursor.SetPosition(0, 0);
|
||||
Write(new ControlSequence(CUP(0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using static Spectre.Console.AnsiSequences;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
@ -15,11 +16,11 @@ namespace Spectre.Console
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
_backend.Write(new ControlSequence("\u001b[?25h"));
|
||||
_backend.Write(new ControlSequence(SM(DECTCEM)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_backend.Write(new ControlSequence("\u001b[?25l"));
|
||||
_backend.Write(new ControlSequence(RM(DECTCEM)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,23 +34,23 @@ namespace Spectre.Console
|
||||
switch (direction)
|
||||
{
|
||||
case CursorDirection.Up:
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}A"));
|
||||
_backend.Write(new ControlSequence(CUU(steps)));
|
||||
break;
|
||||
case CursorDirection.Down:
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}B"));
|
||||
_backend.Write(new ControlSequence(CUD(steps)));
|
||||
break;
|
||||
case CursorDirection.Right:
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}C"));
|
||||
_backend.Write(new ControlSequence(CUF(steps)));
|
||||
break;
|
||||
case CursorDirection.Left:
|
||||
_backend.Write(new ControlSequence($"\u001b[{steps}D"));
|
||||
_backend.Write(new ControlSequence(CUB(steps)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPosition(int column, int line)
|
||||
{
|
||||
_backend.Write(new ControlSequence($"\u001b[{line};{column}H"));
|
||||
_backend.Write(new ControlSequence(CUP(line, column)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
169
src/Spectre.Console/Internal/Backends/Ansi/AnsiSequences.cs
Normal file
169
src/Spectre.Console/Internal/Backends/Ansi/AnsiSequences.cs
Normal file
@ -0,0 +1,169 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class AnsiSequences
|
||||
{
|
||||
/// <summary>
|
||||
/// Introduces a control sequence that uses 8-bit characters.
|
||||
/// </summary>
|
||||
public const string CSI = "\u001b";
|
||||
|
||||
/// <summary>
|
||||
/// Text cursor enable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/DECRQM.html#T5-8"/>.
|
||||
/// </remarks>
|
||||
public const int DECTCEM = 25;
|
||||
|
||||
/// <summary>
|
||||
/// This control function selects one or more character attributes at the same time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/SGR.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string SGR(params int[] codes)
|
||||
{
|
||||
return CSI + "[" + string.Join(";", codes.Select(c => c.ToString())) + "m";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This control function selects one or more character attributes at the same time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/SGR.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string SGR(params byte[] codes)
|
||||
{
|
||||
return CSI + "[" + string.Join(";", codes.Select(c => c.ToString())) + "m";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This control function erases characters from part or all of the display.
|
||||
/// When you erase complete lines, they become single-height, single-width lines,
|
||||
/// with all visual character attributes cleared.
|
||||
/// ED works inside or outside the scrolling margins.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/ED.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string ED(int code)
|
||||
{
|
||||
return CSI + $"[{code}J";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the cursor up a specified number of lines in the same column.
|
||||
/// The cursor stops at the top margin.
|
||||
/// If the cursor is already above the top margin, then the cursor stops at the top line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/CUU.html"/>.
|
||||
/// </remarks>
|
||||
/// <param name="steps">The number of steps to move up.</param>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string CUU(int steps)
|
||||
{
|
||||
return CSI + $"[{steps}A";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This control function moves the cursor down a specified number of lines in the same column.
|
||||
/// The cursor stops at the bottom margin.
|
||||
/// If the cursor is already below the bottom margin, then the cursor stops at the bottom line.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/CUD.html"/>.
|
||||
/// </remarks>
|
||||
/// <param name="steps">The number of steps to move down.</param>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string CUD(int steps)
|
||||
{
|
||||
return CSI + $"[{steps}B";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This control function moves the cursor to the right by a specified number of columns.
|
||||
/// The cursor stops at the right border of the page.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/CUF.html"/>.
|
||||
/// </remarks>
|
||||
/// <param name="steps">The number of steps to move forward.</param>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string CUF(int steps)
|
||||
{
|
||||
return CSI + $"[{steps}C";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This control function moves the cursor to the left by a specified number of columns.
|
||||
/// The cursor stops at the left border of the page.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/CUB.html"/>.
|
||||
/// </remarks>
|
||||
/// <param name="steps">The number of steps to move backward.</param>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string CUB(int steps)
|
||||
{
|
||||
return CSI + $"[{steps}D";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the cursor to the specified position.
|
||||
/// </summary>
|
||||
/// <param name="line">The line to move to.</param>
|
||||
/// <param name="column">The column to move to.</param>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/CUP.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string CUP(int line, int column)
|
||||
{
|
||||
return CSI + $"[{line};{column}H";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the cursor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/RM.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string RM(int code)
|
||||
{
|
||||
return CSI + $"[?{code}l";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the cursor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/SM.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string SM(int code)
|
||||
{
|
||||
return CSI + $"[?{code}h";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This control function erases characters on the line that has the cursor.
|
||||
/// EL clears all character attributes from erased character positions.
|
||||
/// EL works inside or outside the scrolling margins.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see href="https://vt100.net/docs/vt510-rm/EL.html"/>.
|
||||
/// </remarks>
|
||||
/// <returns>The ANSI escape code.</returns>
|
||||
public static string EL(int code)
|
||||
{
|
||||
return CSI + $"[{code}K";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using static Spectre.Console.AnsiSequences;
|
||||
|
||||
namespace Spectre.Console.Rendering
|
||||
{
|
||||
@ -27,7 +28,8 @@ namespace Spectre.Console.Rendering
|
||||
return new ControlSequence(string.Empty);
|
||||
}
|
||||
|
||||
return new ControlSequence("\r" + "\u001b[1A".Repeat(_shape.Value.Height - 1));
|
||||
var linesToMoveUp = _shape.Value.Height - 1;
|
||||
return new ControlSequence("\r" + CUU(linesToMoveUp));
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +42,8 @@ namespace Spectre.Console.Rendering
|
||||
return new ControlSequence(string.Empty);
|
||||
}
|
||||
|
||||
return new ControlSequence("\r\u001b[2K" + "\u001b[1A\u001b[2K".Repeat(_shape.Value.Height - 1));
|
||||
var linesToClear = _shape.Value.Height - 1;
|
||||
return new ControlSequence("\r" + EL(2) + (CUU(1) + EL(2)).Repeat(linesToClear));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user