mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 17:15:28 +08:00 
			
		
		
		
	Add method to write VT/ANSI control codes
Adds a new extension method IAnsiConsole.WriteAnsi that writes VT/ANSI control codes to the console. If VT/ANSI control codes are not supported, nothing will be written.
This commit is contained in:
		 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Phil Scott
						Phil Scott
					
				
			
			
				
	
			
			
			 Phil Scott
						Phil Scott
					
				
			
						parent
						
							91f910326c
						
					
				
				
					commit
					3463dde543
				
			| @@ -17,6 +17,18 @@ namespace Spectre.Console.Testing | |||||||
|             return console; |             return console; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Sets whether or not ANSI is supported. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="console">The console.</param> | ||||||
|  |         /// <param name="enable">Whether or not VT/ANSI control codes are supported.</param> | ||||||
|  |         /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||||
|  |         public static TestConsole SupportsAnsi(this TestConsole console, bool enable) | ||||||
|  |         { | ||||||
|  |             console.Profile.Capabilities.Ansi = enable; | ||||||
|  |             return console; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Makes the console interactive. |         /// Makes the console interactive. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/Spectre.Console.Tests/Unit/AnsiConsoleTests.Advanced.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | using Shouldly; | ||||||
|  | using Spectre.Console.Advanced; | ||||||
|  | using Spectre.Console.Testing; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.Unit | ||||||
|  | { | ||||||
|  |     public partial class AnsiConsoleTests | ||||||
|  |     { | ||||||
|  |         public sealed class Advanced | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Write_Ansi_Codes_To_Console_If_Supported() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var console = new TestConsole() | ||||||
|  |                     .SupportsAnsi(true) | ||||||
|  |                     .Colors(ColorSystem.Standard) | ||||||
|  |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 console.WriteAnsi("[101mHello[0m"); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 console.Output.NormalizeLineEndings() | ||||||
|  |                     .ShouldBe("[101mHello[0m"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Not_Write_Ansi_Codes_To_Console_If_Not_Supported() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var console = new TestConsole() | ||||||
|  |                     .SupportsAnsi(false) | ||||||
|  |                     .Colors(ColorSystem.Standard) | ||||||
|  |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 console.WriteAnsi("[101mHello[0m"); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 console.Output.NormalizeLineEndings() | ||||||
|  |                     .ShouldBeEmpty(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -7,103 +7,109 @@ namespace Spectre.Console.Tests.Unit | |||||||
| { | { | ||||||
|     public partial class AnsiConsoleTests |     public partial class AnsiConsoleTests | ||||||
|     { |     { | ||||||
|         [Theory] |         public sealed class Clear | ||||||
|         [InlineData(false, "Hello[2J[3JWorld")] |  | ||||||
|         [InlineData(true, "Hello[2J[3J[1;1HWorld")] |  | ||||||
|         public void Should_Clear_Screen(bool home, string expected) |  | ||||||
|         { |         { | ||||||
|             // Given |             [Theory] | ||||||
|             var console = new TestConsole() |             [InlineData(false, "Hello[2J[3JWorld")] | ||||||
|                 .Colors(ColorSystem.Standard) |             [InlineData(true, "Hello[2J[3J[1;1HWorld")] | ||||||
|                 .EmitAnsiSequences(); |             public void Should_Clear_Screen(bool home, string expected) | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var console = new TestConsole() | ||||||
|  |                     .Colors(ColorSystem.Standard) | ||||||
|  |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|             // When |                 // When | ||||||
|             console.Write("Hello"); |                 console.Write("Hello"); | ||||||
|             console.Clear(home); |                 console.Clear(home); | ||||||
|             console.Write("World"); |                 console.Write("World"); | ||||||
|  |  | ||||||
|             // Then |                 // Then | ||||||
|             console.Output.ShouldBe(expected); |                 console.Output.ShouldBe(expected); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         [Fact] |         public sealed class Write | ||||||
|         public void Should_Combine_Decoration_And_Colors() |  | ||||||
|         { |         { | ||||||
|             // Given |             [Fact] | ||||||
|             var console = new TestConsole() |             public void Should_Combine_Decoration_And_Colors() | ||||||
|                 .Colors(ColorSystem.Standard) |             { | ||||||
|                 .EmitAnsiSequences(); |                 // Given | ||||||
|  |                 var console = new TestConsole() | ||||||
|  |                     .Colors(ColorSystem.Standard) | ||||||
|  |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|             // When |                 // When | ||||||
|             console.Write( |                 console.Write( | ||||||
|                 "Hello", |                     "Hello", | ||||||
|                 new Style() |                     new Style() | ||||||
|                     .Foreground(Color.RoyalBlue1) |                         .Foreground(Color.RoyalBlue1) | ||||||
|                     .Background(Color.NavajoWhite1) |                         .Background(Color.NavajoWhite1) | ||||||
|                     .Decoration(Decoration.Italic)); |                         .Decoration(Decoration.Italic)); | ||||||
|  |  | ||||||
|             // Then |                 // Then | ||||||
|             console.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m"); |                 console.Output.ShouldBe("\u001b[3;90;47mHello\u001b[0m"); | ||||||
|         } |             } | ||||||
|  |  | ||||||
|         [Fact] |             [Fact] | ||||||
|         public void Should_Not_Include_Foreground_If_Set_To_Default_Color() |             public void Should_Not_Include_Foreground_If_Set_To_Default_Color() | ||||||
|         { |             { | ||||||
|             // Given |                 // Given | ||||||
|             var console = new TestConsole() |                 var console = new TestConsole() | ||||||
|                 .Colors(ColorSystem.Standard) |                     .Colors(ColorSystem.Standard) | ||||||
|                 .EmitAnsiSequences(); |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|             // When |                 // When | ||||||
|             console.Write( |                 console.Write( | ||||||
|                 "Hello", |                     "Hello", | ||||||
|                 new Style() |                     new Style() | ||||||
|                     .Foreground(Color.Default) |                         .Foreground(Color.Default) | ||||||
|                     .Background(Color.NavajoWhite1) |                         .Background(Color.NavajoWhite1) | ||||||
|                     .Decoration(Decoration.Italic)); |                         .Decoration(Decoration.Italic)); | ||||||
|  |  | ||||||
|             // Then |                 // Then | ||||||
|             console.Output.ShouldBe("\u001b[3;47mHello\u001b[0m"); |                 console.Output.ShouldBe("\u001b[3;47mHello\u001b[0m"); | ||||||
|         } |             } | ||||||
|  |  | ||||||
|         [Fact] |             [Fact] | ||||||
|         public void Should_Not_Include_Background_If_Set_To_Default_Color() |             public void Should_Not_Include_Background_If_Set_To_Default_Color() | ||||||
|         { |             { | ||||||
|             // Given |                 // Given | ||||||
|             var console = new TestConsole() |                 var console = new TestConsole() | ||||||
|                 .Colors(ColorSystem.Standard) |                     .Colors(ColorSystem.Standard) | ||||||
|                 .EmitAnsiSequences(); |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|             // When |                 // When | ||||||
|             console.Write( |                 console.Write( | ||||||
|                 "Hello", |                     "Hello", | ||||||
|                 new Style() |                     new Style() | ||||||
|                     .Foreground(Color.RoyalBlue1) |                         .Foreground(Color.RoyalBlue1) | ||||||
|                     .Background(Color.Default) |                         .Background(Color.Default) | ||||||
|                     .Decoration(Decoration.Italic)); |                         .Decoration(Decoration.Italic)); | ||||||
|  |  | ||||||
|             // Then |                 // Then | ||||||
|             console.Output.ShouldBe("\u001b[3;90mHello\u001b[0m"); |                 console.Output.ShouldBe("\u001b[3;90mHello\u001b[0m"); | ||||||
|         } |             } | ||||||
|  |  | ||||||
|         [Fact] |             [Fact] | ||||||
|         public void Should_Not_Include_Decoration_If_Set_To_None() |             public void Should_Not_Include_Decoration_If_Set_To_None() | ||||||
|         { |             { | ||||||
|             // Given |                 // Given | ||||||
|             var console = new TestConsole() |                 var console = new TestConsole() | ||||||
|                 .Colors(ColorSystem.Standard) |                     .Colors(ColorSystem.Standard) | ||||||
|                 .EmitAnsiSequences(); |                     .EmitAnsiSequences(); | ||||||
|  |  | ||||||
|             // When |                 // When | ||||||
|             console.Write( |                 console.Write( | ||||||
|                 "Hello", |                     "Hello", | ||||||
|                 new Style() |                     new Style() | ||||||
|                     .Foreground(Color.RoyalBlue1) |                         .Foreground(Color.RoyalBlue1) | ||||||
|                     .Background(Color.NavajoWhite1) |                         .Background(Color.NavajoWhite1) | ||||||
|                     .Decoration(Decoration.None)); |                         .Decoration(Decoration.None)); | ||||||
|  |  | ||||||
|             // Then |                 // Then | ||||||
|             console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); |                 console.Output.ShouldBe("\u001b[90;47mHello\u001b[0m"); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public sealed class WriteLine |         public sealed class WriteLine | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ namespace Spectre.Console | |||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Gets or sets a value indicating whether or not |         /// Gets or sets a value indicating whether or not | ||||||
|         /// the console supports Ansi. |         /// the console supports VT/ANSI control codes. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public bool Ansi { get; set; } |         public bool Ansi { get; set; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Advanced | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Contains extension methods for <see cref="IAnsiConsole"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class AnsiConsoleExtensions | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Writes a VT/Ansi control code sequence to the console (if supported). | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="console">The console to write to.</param> | ||||||
|  |         /// <param name="sequence">The VT/Ansi control code sequence to write.</param> | ||||||
|  |         public static void WriteAnsi(this IAnsiConsole console, string sequence) | ||||||
|  |         { | ||||||
|  |             if (console is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(console)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (console.Profile.Capabilities.Ansi) | ||||||
|  |             { | ||||||
|  |                 console.Write(new ControlCode(sequence)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -38,6 +38,11 @@ namespace Spectre.Console | |||||||
|         /// <param name="text">The text to write.</param> |         /// <param name="text">The text to write.</param> | ||||||
|         public static void Write(this IAnsiConsole console, string text) |         public static void Write(this IAnsiConsole console, string text) | ||||||
|         { |         { | ||||||
|  |             if (console is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(console)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             console.Write(new Text(text, Style.Plain)); |             console.Write(new Text(text, Style.Plain)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -49,6 +54,11 @@ namespace Spectre.Console | |||||||
|         /// <param name="style">The text style.</param> |         /// <param name="style">The text style.</param> | ||||||
|         public static void Write(this IAnsiConsole console, string text, Style style) |         public static void Write(this IAnsiConsole console, string text, Style style) | ||||||
|         { |         { | ||||||
|  |             if (console is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(console)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             console.Write(new Text(text, style)); |             console.Write(new Text(text, style)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ namespace Spectre.Console | |||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Gets a value indicating whether or not |         /// Gets a value indicating whether or not | ||||||
|         /// the console supports Ansi. |         /// the console supports VT/ANSI control codes. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         bool Ansi { get; } |         bool Ansi { get; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,12 +22,12 @@ namespace Spectre.Console | |||||||
|  |  | ||||||
|         public void Clear(bool home) |         public void Clear(bool home) | ||||||
|         { |         { | ||||||
|             Write(new ControlSequence(ED(2))); |             Write(new ControlCode(ED(2))); | ||||||
|             Write(new ControlSequence(ED(3))); |             Write(new ControlCode(ED(3))); | ||||||
|  |  | ||||||
|             if (home) |             if (home) | ||||||
|             { |             { | ||||||
|                 Write(new ControlSequence(CUP(1, 1))); |                 Write(new ControlCode(CUP(1, 1))); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,11 +16,11 @@ namespace Spectre.Console | |||||||
|         { |         { | ||||||
|             if (show) |             if (show) | ||||||
|             { |             { | ||||||
|                 _backend.Write(new ControlSequence(SM(DECTCEM))); |                 _backend.Write(new ControlCode(SM(DECTCEM))); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 _backend.Write(new ControlSequence(RM(DECTCEM))); |                 _backend.Write(new ControlCode(RM(DECTCEM))); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -34,23 +34,23 @@ namespace Spectre.Console | |||||||
|             switch (direction) |             switch (direction) | ||||||
|             { |             { | ||||||
|                 case CursorDirection.Up: |                 case CursorDirection.Up: | ||||||
|                     _backend.Write(new ControlSequence(CUU(steps))); |                     _backend.Write(new ControlCode(CUU(steps))); | ||||||
|                     break; |                     break; | ||||||
|                 case CursorDirection.Down: |                 case CursorDirection.Down: | ||||||
|                     _backend.Write(new ControlSequence(CUD(steps))); |                     _backend.Write(new ControlCode(CUD(steps))); | ||||||
|                     break; |                     break; | ||||||
|                 case CursorDirection.Right: |                 case CursorDirection.Right: | ||||||
|                     _backend.Write(new ControlSequence(CUF(steps))); |                     _backend.Write(new ControlCode(CUF(steps))); | ||||||
|                     break; |                     break; | ||||||
|                 case CursorDirection.Left: |                 case CursorDirection.Left: | ||||||
|                     _backend.Write(new ControlSequence(CUB(steps))); |                     _backend.Write(new ControlCode(CUB(steps))); | ||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void SetPosition(int column, int line) |         public void SetPosition(int column, int line) | ||||||
|         { |         { | ||||||
|             _backend.Write(new ControlSequence(CUP(line, column))); |             _backend.Write(new ControlCode(CUP(line, column))); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,11 +25,11 @@ namespace Spectre.Console.Rendering | |||||||
|             { |             { | ||||||
|                 if (_shape == null) |                 if (_shape == null) | ||||||
|                 { |                 { | ||||||
|                     return new ControlSequence(string.Empty); |                     return new ControlCode(string.Empty); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 var linesToMoveUp = _shape.Value.Height - 1; |                 var linesToMoveUp = _shape.Value.Height - 1; | ||||||
|                 return new ControlSequence("\r" + CUU(linesToMoveUp)); |                 return new ControlCode("\r" + CUU(linesToMoveUp)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -39,11 +39,11 @@ namespace Spectre.Console.Rendering | |||||||
|             { |             { | ||||||
|                 if (_shape == null) |                 if (_shape == null) | ||||||
|                 { |                 { | ||||||
|                     return new ControlSequence(string.Empty); |                     return new ControlCode(string.Empty); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 var linesToClear = _shape.Value.Height - 1; |                 var linesToClear = _shape.Value.Height - 1; | ||||||
|                 return new ControlSequence("\r" + EL(2) + (CUU(1) + EL(2)).Repeat(linesToClear)); |                 return new ControlCode("\r" + EL(2) + (CUU(1) + EL(2)).Repeat(linesToClear)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,11 @@ namespace Spectre.Console.Rendering | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         public ColorSystem ColorSystem => _capabilities.ColorSystem; |         public ColorSystem ColorSystem => _capabilities.ColorSystem; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets a value indicating whether or not VT/Ansi codes are supported. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool Ansi => _capabilities.Ansi; | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Gets a value indicating whether or not unicode is supported. |         /// Gets a value indicating whether or not unicode is supported. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|   | |||||||
| @@ -3,11 +3,11 @@ using Spectre.Console.Rendering; | |||||||
| 
 | 
 | ||||||
| namespace Spectre.Console | namespace Spectre.Console | ||||||
| { | { | ||||||
|     internal sealed class ControlSequence : Renderable |     internal sealed class ControlCode : Renderable | ||||||
|     { |     { | ||||||
|         private readonly Segment _segment; |         private readonly Segment _segment; | ||||||
| 
 | 
 | ||||||
|         public ControlSequence(string control) |         public ControlCode(string control) | ||||||
|         { |         { | ||||||
|             _segment = Segment.Control(control); |             _segment = Segment.Control(control); | ||||||
|         } |         } | ||||||
| @@ -19,7 +19,10 @@ namespace Spectre.Console | |||||||
| 
 | 
 | ||||||
|         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) |         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||||
|         { |         { | ||||||
|             yield return _segment; |             if (context.Ansi) | ||||||
|  |             { | ||||||
|  |                 yield return _segment; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -73,7 +73,7 @@ namespace Spectre.Console | |||||||
|         public void Refresh() |         public void Refresh() | ||||||
|         { |         { | ||||||
|             _renderer.Update(this); |             _renderer.Update(this); | ||||||
|             _console.Write(new ControlSequence(string.Empty)); |             _console.Write(new ControlCode(string.Empty)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         internal IReadOnlyList<ProgressTask> GetTasks() |         internal IReadOnlyList<ProgressTask> GetTasks() | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ namespace Spectre.Console | |||||||
|  |  | ||||||
|         public void Redraw() |         public void Redraw() | ||||||
|         { |         { | ||||||
|             _console.Write(new ControlSequence(string.Empty)); |             _console.Write(new ControlCode(string.Empty)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public bool Update(ConsoleKey key) |         public bool Update(ConsoleKey key) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user