mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 09:09:25 +08:00 
			
		
		
		
	Added initial support for rendering composites
This is far from complete, but it's a start and it will enable us to create things like tables and other complex objects in the long run.
This commit is contained in:
		 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							e596e6eb4f
						
					
				
				
					commit
					8e4f33bba4
				
			| @@ -13,7 +13,7 @@ namespace Sample | ||||
|             AnsiConsole.WriteLine("Hello World!"); | ||||
|             AnsiConsole.Reset(); | ||||
|             AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities); | ||||
|             AnsiConsole.WriteLine($"Width={AnsiConsole.Width}, Height={AnsiConsole.Height}"); | ||||
|             AnsiConsole.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height); | ||||
|             AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!"); | ||||
|             AnsiConsole.WriteLine(); | ||||
|  | ||||
| @@ -44,13 +44,41 @@ namespace Sample | ||||
|             console.WriteLine("Hello World!"); | ||||
|             console.ResetColors(); | ||||
|             console.ResetStyle(); | ||||
|             console.WriteLine("Capabilities: {0}", AnsiConsole.Capabilities); | ||||
|             console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height); | ||||
|             console.WriteLine("Good bye!"); | ||||
|             console.MarkupLine("Capabilities: [yellow underline]{0}[/]", console.Capabilities); | ||||
|             console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", console.Width, console.Height); | ||||
|             console.MarkupLine("[white on red]Good[/] [red]bye[/]!"); | ||||
|             console.WriteLine(); | ||||
|  | ||||
|             // Nest some panels and text | ||||
|             AnsiConsole.Foreground = Color.Maroon; | ||||
|             AnsiConsole.Render(new Panel(new Panel(new Panel(new Panel( | ||||
|                 Text.New( | ||||
|                     "I heard you like 📦\n\n\n\nSo I put a 📦 in a 📦", | ||||
|                     foreground: Color.White, | ||||
|                     justify: Justify.Center)))))); | ||||
|  | ||||
|             // Reset colors | ||||
|             AnsiConsole.ResetColors(); | ||||
|  | ||||
|             // Left adjusted panel with text | ||||
|             AnsiConsole.Render(new Panel( | ||||
|                 Text.New("Left adjusted\nLeft", | ||||
|                     foreground: Color.White), | ||||
|                 fit: true)); | ||||
|  | ||||
|             // Centered panel with text | ||||
|             AnsiConsole.Render(new Panel( | ||||
|                 Text.New("Centered\nCenter", | ||||
|                     foreground: Color.White, | ||||
|                     justify: Justify.Center), | ||||
|                 fit: true)); | ||||
|  | ||||
|             // Right adjusted panel with text | ||||
|             AnsiConsole.Render(new Panel( | ||||
|                 Text.New("Right adjusted\nRight", | ||||
|                     foreground: Color.White, | ||||
|                     justify: Justify.Right), | ||||
|                 fit: true)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
							
								
								
									
										42
									
								
								src/Spectre.Console.Tests/PlainConsole.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/Spectre.Console.Tests/PlainConsole.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Tests | ||||
| { | ||||
|     public sealed class PlainConsole : IAnsiConsole, IDisposable | ||||
|     { | ||||
|         public Capabilities Capabilities => throw new NotSupportedException(); | ||||
|         public Encoding Encoding { get; } | ||||
|  | ||||
|         public int Width { get; } | ||||
|         public int Height { get; } | ||||
|  | ||||
|         public Styles Style { get; set; } | ||||
|         public Color Foreground { get; set; } | ||||
|         public Color Background { get; set; } | ||||
|  | ||||
|         public StringWriter Writer { get; } | ||||
|         public string Output => Writer.ToString().TrimEnd('\n'); | ||||
|         public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' }); | ||||
|  | ||||
|         public PlainConsole(int width = 80, int height = 9000, Encoding encoding = null) | ||||
|         { | ||||
|             Width = width; | ||||
|             Height = height; | ||||
|             Encoding = encoding ?? Encoding.UTF8; | ||||
|             Writer = new StringWriter(); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             Writer.Dispose(); | ||||
|         } | ||||
|  | ||||
|         public void Write(string text) | ||||
|         { | ||||
|             Writer.Write(text); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										126
									
								
								src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/Spectre.Console.Tests/Unit/Composition/PanelTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| using Shouldly; | ||||
| using Spectre.Console.Composition; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit.Composition | ||||
| { | ||||
|     public sealed class PanelTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Render_Panel() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 80); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Text("Hello World"))); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
|             console.Lines[0].ShouldBe("┌─────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│ Hello World │"); | ||||
|             console.Lines[2].ShouldBe("└─────────────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Render_Panel_With_Unicode_Correctly() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 80); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Text(" \n💩\n "))); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(5); | ||||
|             console.Lines[0].ShouldBe("┌────┐"); | ||||
|             console.Lines[1].ShouldBe("│    │"); | ||||
|             console.Lines[2].ShouldBe("│ 💩 │"); | ||||
|             console.Lines[3].ShouldBe("│    │"); | ||||
|             console.Lines[4].ShouldBe("└────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Render_Panel_With_Multiple_Lines() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 80); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Text("Hello World\nFoo Bar"))); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(4); | ||||
|             console.Lines[0].ShouldBe("┌─────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│ Hello World │"); | ||||
|             console.Lines[2].ShouldBe("│ Foo Bar     │"); | ||||
|             console.Lines[3].ShouldBe("└─────────────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Fit_Panel_To_Parent_If_Enabled() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 25); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Text("Hello World"), fit: true)); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
|             console.Lines[0].ShouldBe("┌───────────────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│ Hello World           │"); | ||||
|             console.Lines[2].ShouldBe("└───────────────────────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Justify_Child_To_Right() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 25); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Text("Hello World", justify: Justify.Right), fit: true)); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
|             console.Lines[0].ShouldBe("┌───────────────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│           Hello World │"); | ||||
|             console.Lines[2].ShouldBe("└───────────────────────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Justify_Child_To_Center() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 25); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Text("Hello World", justify: Justify.Center), fit: true)); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
|             console.Lines[0].ShouldBe("┌───────────────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│      Hello World      │"); | ||||
|             console.Lines[2].ShouldBe("└───────────────────────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Render_Panel_Inside_Panel_Correctly() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 80); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Panel(new Panel(new Text("Hello World")))); | ||||
|  | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(5); | ||||
|             console.Lines[0].ShouldBe("┌─────────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│ ┌─────────────┐ │"); | ||||
|             console.Lines[2].ShouldBe("│ │ Hello World │ │"); | ||||
|             console.Lines[3].ShouldBe("│ └─────────────┘ │"); | ||||
|             console.Lines[4].ShouldBe("└─────────────────┘"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										67
									
								
								src/Spectre.Console.Tests/Unit/Composition/SegmentTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/Spectre.Console.Tests/Unit/Composition/SegmentTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| using Shouldly; | ||||
| using Spectre.Console.Composition; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit.Composition | ||||
| { | ||||
|     public sealed class SegmentTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Split_Segment() | ||||
|         { | ||||
|             var lines = Segment.Split(new[] | ||||
|             { | ||||
|                 new Segment("Foo"), | ||||
|                 new Segment("Bar"), | ||||
|                 new Segment("\n"), | ||||
|                 new Segment("Baz"), | ||||
|                 new Segment("Qux"), | ||||
|                 new Segment("\n"), | ||||
|                 new Segment("Corgi"), | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             lines.Count.ShouldBe(3); | ||||
|  | ||||
|             lines[0].Count.ShouldBe(2); | ||||
|             lines[0][0].Text.ShouldBe("Foo"); | ||||
|             lines[0][1].Text.ShouldBe("Bar"); | ||||
|  | ||||
|             lines[1].Count.ShouldBe(2); | ||||
|             lines[1][0].Text.ShouldBe("Baz"); | ||||
|             lines[1][1].Text.ShouldBe("Qux"); | ||||
|  | ||||
|             lines[2].Count.ShouldBe(1); | ||||
|             lines[2][0].Text.ShouldBe("Corgi"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Split_Segments_With_Linebreak_In_Text() | ||||
|         { | ||||
|             var lines = Segment.Split(new[] | ||||
|             { | ||||
|                 new Segment("Foo\n"), | ||||
|                 new Segment("Bar\n"), | ||||
|                 new Segment("Baz"), | ||||
|                 new Segment("Qux\n"), | ||||
|                 new Segment("Corgi"), | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             lines.Count.ShouldBe(4); | ||||
|  | ||||
|             lines[0].Count.ShouldBe(1); | ||||
|             lines[0][0].Text.ShouldBe("Foo"); | ||||
|  | ||||
|             lines[1].Count.ShouldBe(1); | ||||
|             lines[1][0].Text.ShouldBe("Bar"); | ||||
|  | ||||
|             lines[2].Count.ShouldBe(2); | ||||
|             lines[2][0].Text.ShouldBe("Baz"); | ||||
|             lines[2][1].Text.ShouldBe("Qux"); | ||||
|  | ||||
|             lines[3].Count.ShouldBe(1); | ||||
|             lines[3][0].Text.ShouldBe("Corgi"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										61
									
								
								src/Spectre.Console.Tests/Unit/Composition/TextTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/Spectre.Console.Tests/Unit/Composition/TextTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| using Shouldly; | ||||
| using Spectre.Console.Composition; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Composition | ||||
| { | ||||
|     public sealed class TextTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Render_Text_To_Console() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Text("Hello World")); | ||||
|  | ||||
|             // Then | ||||
|             console.Output.ShouldBe("Hello World"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Right_Align_Text_To_Parent() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 15); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Text("Hello World", justify: Justify.Right)); | ||||
|  | ||||
|             // Then | ||||
|             console.Output.ShouldBe("    Hello World"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Center_Text_To_Parent() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 15); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Text("Hello World", justify: Justify.Center)); | ||||
|  | ||||
|             // Then | ||||
|             console.Output.ShouldBe("  Hello World  "); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Split_Text_To_Multiple_Lines_If_It_Does_Not_Fit() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 5); | ||||
|  | ||||
|             // When | ||||
|             console.Render(new Text("Hello World")); | ||||
|  | ||||
|             // Then | ||||
|             console.Output.ShouldBe("Hello\n Worl\nd"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/Spectre.Console/AnsiConsole.Rendering.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Spectre.Console/AnsiConsole.Rendering.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| using Spectre.Console.Composition; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A console capable of writing ANSI escape sequences. | ||||
|     /// </summary> | ||||
|     public static partial class AnsiConsole | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Renders the specified object to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="renderable">The object to render.</param> | ||||
|         public static void Render(IRenderable renderable) | ||||
|         { | ||||
|             Console.Render(renderable); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -26,7 +26,7 @@ namespace Spectre.Console | ||||
|         /// <summary> | ||||
|         /// Gets the console's capabilities. | ||||
|         /// </summary> | ||||
|         public static AnsiConsoleCapabilities Capabilities => Console.Capabilities; | ||||
|         public static Capabilities Capabilities => Console.Capabilities; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the buffer width of the console. | ||||
|   | ||||
							
								
								
									
										86
									
								
								src/Spectre.Console/Appearance.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/Spectre.Console/Appearance.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents color and style. | ||||
|     /// </summary> | ||||
|     public sealed class Appearance : IEquatable<Appearance> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the foreground color. | ||||
|         /// </summary> | ||||
|         public Color Foreground { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the background color. | ||||
|         /// </summary> | ||||
|         public Color Background { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the style. | ||||
|         /// </summary> | ||||
|         public Styles Style { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets an <see cref="Appearance"/> with the | ||||
|         /// default color and without style. | ||||
|         /// </summary> | ||||
|         public static Appearance Plain { get; } | ||||
|  | ||||
|         static Appearance() | ||||
|         { | ||||
|             Plain = new Appearance(); | ||||
|         } | ||||
|  | ||||
|         private Appearance() | ||||
|             : this(null, null, null) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Appearance"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="foreground">The foreground color.</param> | ||||
|         /// <param name="background">The background color.</param> | ||||
|         /// <param name="style">The style.</param> | ||||
|         public Appearance(Color? foreground = null, Color? background = null, Styles? style = null) | ||||
|         { | ||||
|             Foreground = foreground ?? Color.Default; | ||||
|             Background = background ?? Color.Default; | ||||
|             Style = style ?? Styles.None; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override int GetHashCode() | ||||
|         { | ||||
|             unchecked | ||||
|             { | ||||
|                 var hash = (int)2166136261; | ||||
|                 hash = (hash * 16777619) ^ Foreground.GetHashCode(); | ||||
|                 hash = (hash * 16777619) ^ Background.GetHashCode(); | ||||
|                 hash = (hash * 16777619) ^ Style.GetHashCode(); | ||||
|                 return hash; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override bool Equals(object obj) | ||||
|         { | ||||
|             return Equals(obj as Appearance); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public bool Equals(Appearance other) | ||||
|         { | ||||
|             if (other == null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             return Foreground.Equals(other.Foreground) && | ||||
|                 Background.Equals(other.Background) && | ||||
|                 Style == other.Style; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console | ||||
|     /// <summary> | ||||
|     /// Represents console capabilities. | ||||
|     /// </summary> | ||||
|     public sealed class AnsiConsoleCapabilities | ||||
|     public sealed class Capabilities | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets a value indicating whether or not | ||||
| @@ -16,7 +16,7 @@ namespace Spectre.Console | ||||
|         /// </summary> | ||||
|         public ColorSystem ColorSystem { get; } | ||||
| 
 | ||||
|         internal AnsiConsoleCapabilities(bool supportsAnsi, ColorSystem colorSystem) | ||||
|         internal Capabilities(bool supportsAnsi, ColorSystem colorSystem) | ||||
|         { | ||||
|             SupportsAnsi = supportsAnsi; | ||||
|             ColorSystem = colorSystem; | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Globalization; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| @@ -207,5 +208,11 @@ namespace Spectre.Console | ||||
|                 _ => Default, | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string ToString() | ||||
|         { | ||||
|             return Name ?? string.Format(CultureInfo.InvariantCulture, "#{0:2X}{1:2X}{2:2X}", R, G, B); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										27
									
								
								src/Spectre.Console/Composition/IRenderable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/Spectre.Console/Composition/IRenderable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents something that can be rendered to the console. | ||||
|     /// </summary> | ||||
|     public interface IRenderable | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Measures the renderable object. | ||||
|         /// </summary> | ||||
|         /// <param name="encoding">The encoding to use.</param> | ||||
|         /// <param name="maxWidth">The maximum allowed width.</param> | ||||
|         /// <returns>The width of the object.</returns> | ||||
|         int Measure(Encoding encoding, int maxWidth); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Renders the object. | ||||
|         /// </summary> | ||||
|         /// <param name="encoding">The encoding to use.</param> | ||||
|         /// <param name="width">The width of the render area.</param> | ||||
|         /// <returns>A collection of segments.</returns> | ||||
|         IEnumerable<Segment> Render(Encoding encoding, int width); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										130
									
								
								src/Spectre.Console/Composition/Segment.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/Spectre.Console/Composition/Segment.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a renderable segment. | ||||
|     /// </summary> | ||||
|     public sealed class Segment | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the segment text. | ||||
|         /// </summary> | ||||
|         public string Text { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the appearance of the segment. | ||||
|         /// </summary> | ||||
|         public Appearance Appearance { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Segment"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="text">The segment text.</param> | ||||
|         public Segment(string text) | ||||
|             : this(text, Appearance.Plain) | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Segment"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="text">The segment text.</param> | ||||
|         /// <param name="appearance">The segment appearance.</param> | ||||
|         public Segment(string text, Appearance appearance) | ||||
|         { | ||||
|             Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text)); | ||||
|             Appearance = appearance; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the number of cells that this segment | ||||
|         /// occupies in the console. | ||||
|         /// </summary> | ||||
|         /// <param name="encoding">The encoding to use.</param> | ||||
|         /// <returns>The number of cells that this segment occupies in the console.</returns> | ||||
|         public int CellLength(Encoding encoding) | ||||
|         { | ||||
|             return Text.CellLength(encoding); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns a new segment without any trailing line endings. | ||||
|         /// </summary> | ||||
|         /// <returns>A new segment without any trailing line endings.</returns> | ||||
|         public Segment StripLineEndings() | ||||
|         { | ||||
|             return new Segment(Text.TrimEnd('\n'), Appearance); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Splits the provided segments into lines. | ||||
|         /// </summary> | ||||
|         /// <param name="segments">The segments to split.</param> | ||||
|         /// <returns>A collection of lines.</returns> | ||||
|         public static List<SegmentLine> Split(IEnumerable<Segment> segments) | ||||
|         { | ||||
|             if (segments is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(segments)); | ||||
|             } | ||||
|  | ||||
|             var lines = new List<SegmentLine>(); | ||||
|             var line = new SegmentLine(); | ||||
|  | ||||
|             foreach (var segment in segments) | ||||
|             { | ||||
|                 if (segment.Text.Contains("\n")) | ||||
|                 { | ||||
|                     if (segment.Text == "\n") | ||||
|                     { | ||||
|                         lines.Add(line); | ||||
|                         line = new SegmentLine(); | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     var text = segment.Text; | ||||
|                     while (text != null) | ||||
|                     { | ||||
|                         var parts = text.SplitLines(); | ||||
|                         if (parts.Length > 0) | ||||
|                         { | ||||
|                             line.Add(new Segment(parts[0], segment.Appearance)); | ||||
|                         } | ||||
|  | ||||
|                         if (parts.Length > 1) | ||||
|                         { | ||||
|                             lines.Add(line); | ||||
|                             line = new SegmentLine(); | ||||
|  | ||||
|                             text = string.Concat(parts.Skip(1).Take(parts.Length - 1)); | ||||
|                             if (string.IsNullOrWhiteSpace(text)) | ||||
|                             { | ||||
|                                 text = null; | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             text = null; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     line.Add(segment); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (line.Count > 0) | ||||
|             { | ||||
|                 lines.Add(line); | ||||
|             } | ||||
|  | ||||
|             return lines; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/Spectre.Console/Composition/SegmentLine.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Spectre.Console/Composition/SegmentLine.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a line of segments. | ||||
|     /// </summary> | ||||
|     [SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")] | ||||
|     public sealed class SegmentLine : List<Segment> | ||||
|     { | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								src/Spectre.Console/ConsoleExtensions.Rendering.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Spectre.Console/ConsoleExtensions.Rendering.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| using System; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="IAnsiConsole"/>. | ||||
|     /// </summary> | ||||
|     public static partial class ConsoleExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Renders the specified object to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to render to.</param> | ||||
|         /// <param name="renderable">The object to render.</param> | ||||
|         public static void Render(this IAnsiConsole console, IRenderable renderable) | ||||
|         { | ||||
|             if (console is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(console)); | ||||
|             } | ||||
|  | ||||
|             if (renderable is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(renderable)); | ||||
|             } | ||||
|  | ||||
|             foreach (var segment in renderable.Render(console.Encoding, console.Width)) | ||||
|             { | ||||
|                 if (!segment.Appearance.Equals(Appearance.Plain)) | ||||
|                 { | ||||
|                     using (var appearance = console.PushAppearance(segment.Appearance)) | ||||
|                     { | ||||
|                         console.Write(segment.Text); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     console.Write(segment.Text); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,3 +1,5 @@ | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
| @@ -8,7 +10,7 @@ namespace Spectre.Console | ||||
|         /// <summary> | ||||
|         /// Gets the console's capabilities. | ||||
|         /// </summary> | ||||
|         AnsiConsoleCapabilities Capabilities { get; } | ||||
|         Capabilities Capabilities { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the buffer width of the console. | ||||
| @@ -20,6 +22,11 @@ namespace Spectre.Console | ||||
|         /// </summary> | ||||
|         int Height { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the console output encoding. | ||||
|         /// </summary> | ||||
|         Encoding Encoding { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets the current style. | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class AnsiDetector | ||||
|     { | ||||
|         private static readonly Regex[] Regexes = new[] | ||||
|         private static readonly Regex[] _regexes = new[] | ||||
|         { | ||||
|             new Regex("^xterm"), // xterm, PuTTY, Mintty | ||||
|             new Regex("^rxvt"), // RXVT | ||||
| @@ -32,7 +32,7 @@ namespace Spectre.Console.Internal | ||||
|             new Regex("bvterm"), // Bitvise SSH Client | ||||
|         }; | ||||
|  | ||||
|         public static bool SupportsAnsi(bool upgrade) | ||||
|         public static bool Detect(bool upgrade) | ||||
|         { | ||||
|             // Github action doesn't setup a correct PTY but supports ANSI. | ||||
|             if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION"))) | ||||
| @@ -57,7 +57,7 @@ namespace Spectre.Console.Internal | ||||
|             var term = Environment.GetEnvironmentVariable("TERM"); | ||||
|             if (!string.IsNullOrWhiteSpace(term)) | ||||
|             { | ||||
|                 if (Regexes.Any(regex => regex.IsMatch(term))) | ||||
|                 if (_regexes.Any(regex => regex.IsMatch(term))) | ||||
|                 { | ||||
|                     return true; | ||||
|                 } | ||||
| @@ -71,8 +71,10 @@ namespace Spectre.Console.Internal | ||||
|         { | ||||
|             [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; | ||||
|  | ||||
| @@ -94,7 +96,7 @@ namespace Spectre.Console.Internal | ||||
|                 try | ||||
|                 { | ||||
|                     var @out = GetStdHandle(STD_OUTPUT_HANDLE); | ||||
|                     if (!GetConsoleMode(@out, out uint mode)) | ||||
|                     if (!GetConsoleMode(@out, out var mode)) | ||||
|                     { | ||||
|                         // Could not get console mode. | ||||
|                         return false; | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| 
 | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
| @@ -8,7 +9,8 @@ namespace Spectre.Console.Internal | ||||
|         private readonly TextWriter _out; | ||||
|         private readonly ColorSystem _system; | ||||
| 
 | ||||
|         public AnsiConsoleCapabilities Capabilities { get; } | ||||
|         public Capabilities Capabilities { get; } | ||||
|         public Encoding Encoding { get; } | ||||
|         public Styles Style { get; set; } | ||||
|         public Color Foreground { get; set; } | ||||
|         public Color Background { get; set; } | ||||
| @@ -44,7 +46,8 @@ namespace Spectre.Console.Internal | ||||
|             _out = @out ?? throw new ArgumentNullException(nameof(@out)); | ||||
|             _system = system; | ||||
| 
 | ||||
|             Capabilities = new AnsiConsoleCapabilities(true, system); | ||||
|             Capabilities = new Capabilities(true, system); | ||||
|             Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8; | ||||
|             Foreground = Color.Default; | ||||
|             Background = Color.Default; | ||||
|             Style = Styles.None; | ||||
| @@ -73,7 +76,7 @@ namespace Spectre.Console.Internal | ||||
| 
 | ||||
|             _out.Write(AnsiBuilder.GetAnsi( | ||||
|                 _system, | ||||
|                 text, | ||||
|                 text.NormalizeLineEndings(native: true), | ||||
|                 Style, | ||||
|                 Foreground, | ||||
|                 Background)); | ||||
| @@ -14,7 +14,7 @@ namespace Spectre.Console.Internal | ||||
|             var buffer = settings.Out ?? System.Console.Out; | ||||
|  | ||||
|             var supportsAnsi = settings.Ansi == AnsiSupport.Detect | ||||
|                 ? AnsiDetector.SupportsAnsi(true) | ||||
|                 ? AnsiDetector.Detect(true) | ||||
|                 : settings.Ansi == AnsiSupport.Yes; | ||||
|  | ||||
|             var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect | ||||
|   | ||||
							
								
								
									
										12
									
								
								src/Spectre.Console/Internal/Extensions/CharExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Spectre.Console/Internal/Extensions/CharExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class CharExtensions | ||||
|     { | ||||
|         public static int CellLength(this char token, Encoding encoding) | ||||
|         { | ||||
|             return Cell.GetCellLength(encoding, token); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,10 +1,25 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using Spectre.Console.Composition; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class ConsoleExtensions | ||||
|     { | ||||
|         public static IDisposable PushAppearance(this IAnsiConsole console, Appearance appearance) | ||||
|         { | ||||
|             if (console is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(console)); | ||||
|             } | ||||
|  | ||||
|             var current = new Appearance(console.Foreground, console.Background, console.Style); | ||||
|             console.SetColor(appearance.Foreground, true); | ||||
|             console.SetColor(appearance.Background, false); | ||||
|             console.Style = appearance.Style; | ||||
|             return new AppearanceScope(console, current); | ||||
|         } | ||||
|  | ||||
|         public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground) | ||||
|         { | ||||
|             if (console is null) | ||||
| @@ -47,6 +62,33 @@ namespace Spectre.Console.Internal | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal sealed class AppearanceScope : IDisposable | ||||
|     { | ||||
|         private readonly IAnsiConsole _console; | ||||
|         private readonly Appearance _apperance; | ||||
|  | ||||
|         public AppearanceScope(IAnsiConsole console, Appearance appearance) | ||||
|         { | ||||
|             _console = console ?? throw new ArgumentNullException(nameof(console)); | ||||
|             _apperance = appearance; | ||||
|         } | ||||
|  | ||||
|         [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")] | ||||
|         [SuppressMessage("Performance", "CA1821:Remove empty Finalizers")] | ||||
|         ~AppearanceScope() | ||||
|         { | ||||
|             throw new InvalidOperationException("Appearance scope was not disposed."); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             GC.SuppressFinalize(this); | ||||
|             _console.SetColor(_apperance.Foreground, true); | ||||
|             _console.SetColor(_apperance.Background, false); | ||||
|             _console.Style = _apperance.Style; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal sealed class ColorScope : IDisposable | ||||
|     { | ||||
|         private readonly IAnsiConsole _console; | ||||
|   | ||||
							
								
								
									
										36
									
								
								src/Spectre.Console/Internal/Extensions/StringExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/Spectre.Console/Internal/Extensions/StringExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| using System; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class StringExtensions | ||||
|     { | ||||
|         // Cache whether or not internally normalized line endings | ||||
|         // already are normalized. No reason to do yet another replace if it is. | ||||
|         private static readonly bool _alreadyNormalized | ||||
|             = Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase); | ||||
|  | ||||
|         public static int CellLength(this string text, Encoding encoding) | ||||
|         { | ||||
|             return Cell.GetCellLength(encoding, text); | ||||
|         } | ||||
|  | ||||
|         public static string NormalizeLineEndings(this string text, bool native = false) | ||||
|         { | ||||
|             var normalized = text?.Replace("\r\n", "\n") | ||||
|                 ?.Replace("\r", string.Empty); | ||||
|  | ||||
|             if (native && !_alreadyNormalized) | ||||
|             { | ||||
|                 normalized = normalized.Replace("\n", Environment.NewLine); | ||||
|             } | ||||
|  | ||||
|             return normalized; | ||||
|         } | ||||
|  | ||||
|         public static string[] SplitLines(this string text) | ||||
|         { | ||||
|             return text.NormalizeLineEndings().Split(new[] { '\n' }, StringSplitOptions.None); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| 
 | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
| @@ -13,7 +14,9 @@ namespace Spectre.Console.Internal | ||||
|         private ConsoleColor _foreground; | ||||
|         private ConsoleColor _background; | ||||
| 
 | ||||
|         public AnsiConsoleCapabilities Capabilities { get; } | ||||
|         public Capabilities Capabilities { get; } | ||||
| 
 | ||||
|         public Encoding Encoding { get; } | ||||
| 
 | ||||
|         public int Width | ||||
|         { | ||||
| @@ -87,23 +90,27 @@ namespace Spectre.Console.Internal | ||||
|             _out = @out; | ||||
|             _system = system; | ||||
| 
 | ||||
|             Capabilities = new AnsiConsoleCapabilities(false, _system); | ||||
| 
 | ||||
|             if (_out.IsStandardOut()) | ||||
|             { | ||||
|                 _defaultForeground = System.Console.ForegroundColor; | ||||
|                 _defaultBackground = System.Console.BackgroundColor; | ||||
| 
 | ||||
|                 Encoding = System.Console.OutputEncoding; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _defaultForeground = ConsoleColor.Gray; | ||||
|                 _defaultBackground = ConsoleColor.Black; | ||||
| 
 | ||||
|                 Encoding = Encoding.UTF8; | ||||
|             } | ||||
| 
 | ||||
|             Capabilities = new Capabilities(false, _system); | ||||
|         } | ||||
| 
 | ||||
|         public void Write(string text) | ||||
|         { | ||||
|             _out.Write(text); | ||||
|             _out.Write(text.NormalizeLineEndings(native: true)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										150
									
								
								src/Spectre.Console/Internal/Text/Cell.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/Spectre.Console/Internal/Text/Cell.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| // Taken and modified from NStack project by Miguel de Icaza, licensed under BSD-3 | ||||
| // https://github.com/migueldeicaza/NStack/blob/3fc024fb2c2e99927d3e12991570fb54db8ce01e/NStack/unicode/Rune.ColumnWidth.cs | ||||
|  | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class Cell | ||||
|     { | ||||
|         [SuppressMessage("Design", "RCS1169:Make field read-only.")] | ||||
|         [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces for multi-line statements should not share line")] | ||||
|         private static readonly uint[,] _combining = new uint[,] | ||||
|         { | ||||
|             { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, | ||||
|             { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, | ||||
|             { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, | ||||
|             { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, | ||||
|             { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, | ||||
|             { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, | ||||
|             { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, | ||||
|             { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, | ||||
|             { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, | ||||
|             { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, | ||||
|             { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, | ||||
|             { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, | ||||
|             { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, | ||||
|             { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, | ||||
|             { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, | ||||
|             { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, | ||||
|             { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, | ||||
|             { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, | ||||
|             { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, | ||||
|             { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, | ||||
|             { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, | ||||
|             { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, | ||||
|             { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, | ||||
|             { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, | ||||
|             { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, | ||||
|             { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, | ||||
|             { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, | ||||
|             { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, | ||||
|             { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, | ||||
|             { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, | ||||
|             { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, | ||||
|             { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, | ||||
|             { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, | ||||
|             { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, | ||||
|             { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, | ||||
|             { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, | ||||
|             { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, | ||||
|             { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, | ||||
|             { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, | ||||
|             { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, | ||||
|             { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, | ||||
|             { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, | ||||
|             { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, | ||||
|             { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, | ||||
|             { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, | ||||
|             { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, | ||||
|             { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, | ||||
|             { 0xE0100, 0xE01EF }, | ||||
|         }; | ||||
|  | ||||
|         public static int GetCellLength(Encoding encoding, string text) | ||||
|         { | ||||
|             return text.Sum(c => GetCellLength(encoding, c)); | ||||
|         } | ||||
|  | ||||
|         public static int GetCellLength(Encoding encoding, char rune) | ||||
|         { | ||||
|             // Is it represented by a single byte? | ||||
|             // In that case we don't have to calculate the | ||||
|             // actual cell width. | ||||
|             if (encoding.GetByteCount(new[] { rune }) == 1) | ||||
|             { | ||||
|                 return 1; | ||||
|             } | ||||
|  | ||||
|             var irune = (uint)rune; | ||||
|             if (irune < 32) | ||||
|             { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             if (irune < 127) | ||||
|             { | ||||
|                 return 1; | ||||
|             } | ||||
|  | ||||
|             if (irune >= 0x7f && irune <= 0xa0) | ||||
|             { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             // Binary search in table of non-spacing characters | ||||
|             if (BinarySearch(irune, _combining, _combining.GetLength(0) - 1) != 0) | ||||
|             { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             // If we arrive here, ucs is not a combining or C0/C1 control character | ||||
|             return 1 + | ||||
|                 ((irune >= 0x1100 && | ||||
|                  (irune <= 0x115f || /* Hangul Jamo init. consonants */ | ||||
|                 irune == 0x2329 || irune == 0x232a || | ||||
|                 (irune >= 0x2e80 && irune <= 0xa4cf && | ||||
|                 irune != 0x303f) || /* CJK ... Yi */ | ||||
|                 (irune >= 0xac00 && irune <= 0xd7a3) || /* Hangul Syllables */ | ||||
|                 (irune >= 0xf900 && irune <= 0xfaff) || /* CJK Compatibility Ideographs */ | ||||
|                 (irune >= 0xfe10 && irune <= 0xfe19) || /* Vertical forms */ | ||||
|                 (irune >= 0xfe30 && irune <= 0xfe6f) || /* CJK Compatibility Forms */ | ||||
|                 (irune >= 0xff00 && irune <= 0xff60) || /* Fullwidth Forms */ | ||||
|                 (irune >= 0xffe0 && irune <= 0xffe6) || | ||||
|                 (irune >= 0x20000 && irune <= 0x2fffd) || | ||||
|                   (irune >= 0x30000 && irune <= 0x3fffd))) ? 1 : 0); | ||||
|         } | ||||
|  | ||||
|         private static int BinarySearch(uint rune, uint[,] table, int max) | ||||
|         { | ||||
|             var min = 0; | ||||
|             int mid; | ||||
|  | ||||
|             if (rune < table[0, 0] || rune > table[max, 1]) | ||||
|             { | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             while (max >= min) | ||||
|             { | ||||
|                 mid = (min + max) / 2; | ||||
|                 if (rune > table[mid, 1]) | ||||
|                 { | ||||
|                     min = mid + 1; | ||||
|                 } | ||||
|                 else if (rune < table[mid, 0]) | ||||
|                 { | ||||
|                     max = mid - 1; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     return 1; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src/Spectre.Console/Justify.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/Spectre.Console/Justify.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents text justification. | ||||
|     /// </summary> | ||||
|     public enum Justify | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Left aligned. | ||||
|         /// </summary> | ||||
|         Left = 0, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Right aligned. | ||||
|         /// </summary> | ||||
|         Right = 1, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Centered | ||||
|         /// </summary> | ||||
|         Center = 2, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										80
									
								
								src/Spectre.Console/Renderables/Panel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/Spectre.Console/Renderables/Panel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using Spectre.Console.Composition; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a panel which contains another renderable item. | ||||
|     /// </summary> | ||||
|     public sealed class Panel : IRenderable | ||||
|     { | ||||
|         private readonly IRenderable _child; | ||||
|         private readonly bool _fit; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Panel"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="child">The child.</param> | ||||
|         /// <param name="fit">Whether or not to fit the panel to it's parent.</param> | ||||
|         public Panel(IRenderable child, bool fit = false) | ||||
|         { | ||||
|             _child = child; | ||||
|             _fit = fit; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Measure(Encoding encoding, int maxWidth) | ||||
|         { | ||||
|             var childWidth = _child.Measure(encoding, maxWidth); | ||||
|             return childWidth + 4; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public IEnumerable<Segment> Render(Encoding encoding, int width) | ||||
|         { | ||||
|             var childWidth = width - 4; | ||||
|             if (!_fit) | ||||
|             { | ||||
|                 childWidth = _child.Measure(encoding, width - 2); | ||||
|             } | ||||
|  | ||||
|             var result = new List<Segment>(); | ||||
|             var panelWidth = childWidth + 2; | ||||
|  | ||||
|             result.Add(new Segment("┌")); | ||||
|             result.Add(new Segment(new string('─', panelWidth))); | ||||
|             result.Add(new Segment("┐")); | ||||
|             result.Add(new Segment("\n")); | ||||
|  | ||||
|             var childSegments = _child.Render(encoding, childWidth); | ||||
|             foreach (var line in Segment.Split(childSegments)) | ||||
|             { | ||||
|                 result.Add(new Segment("│ ")); | ||||
|  | ||||
|                 foreach (var segment in line) | ||||
|                 { | ||||
|                     result.Add(segment.StripLineEndings()); | ||||
|                 } | ||||
|  | ||||
|                 var length = line.Sum(segment => segment.CellLength(encoding)); | ||||
|                 if (length < childWidth) | ||||
|                 { | ||||
|                     var diff = childWidth - length; | ||||
|                     result.Add(new Segment(new string(' ', diff))); | ||||
|                 } | ||||
|  | ||||
|                 result.Add(new Segment(" │")); | ||||
|                 result.Add(new Segment("\n")); | ||||
|             } | ||||
|  | ||||
|             result.Add(new Segment("└")); | ||||
|             result.Add(new Segment(new string('─', panelWidth))); | ||||
|             result.Add(new Segment("┘")); | ||||
|             result.Add(new Segment("\n")); | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										127
									
								
								src/Spectre.Console/Renderables/Text.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/Spectre.Console/Renderables/Text.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents text with color and style. | ||||
|     /// </summary> | ||||
|     [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||
|     public sealed class Text : IRenderable | ||||
|     { | ||||
|         private readonly string _text; | ||||
|         private readonly Appearance _appearance; | ||||
|         private readonly Justify _justify; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Text"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="text">The text.</param> | ||||
|         /// <param name="appearance">The appearance.</param> | ||||
|         /// <param name="justify">The justification.</param> | ||||
|         public Text(string text, Appearance appearance = null, Justify justify = Justify.Left) | ||||
|         { | ||||
|             _text = text ?? throw new ArgumentNullException(nameof(text)); | ||||
|             _appearance = appearance ?? Appearance.Plain; | ||||
|             _justify = justify; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Text"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="text">The text.</param> | ||||
|         /// <param name="foreground">The foreground.</param> | ||||
|         /// <param name="background">The background.</param> | ||||
|         /// <param name="style">The style.</param> | ||||
|         /// <param name="justify">The justification.</param> | ||||
|         /// <returns>A <see cref="Text"/> instance.</returns> | ||||
|         public static Text New( | ||||
|             string text, Color? foreground = null, Color? background = null, | ||||
|             Styles? style = null, Justify justify = Justify.Left) | ||||
|         { | ||||
|             return new Text(text, new Appearance(foreground, background, style), justify); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Measure(Encoding encoding, int maxWidth) | ||||
|         { | ||||
|             return _text.SplitLines().Max(x => x.CellLength(encoding)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public IEnumerable<Segment> Render(Encoding encoding, int width) | ||||
|         { | ||||
|             var result = new List<Segment>(); | ||||
|  | ||||
|             foreach (var line in Partition(encoding, _text, width)) | ||||
|             { | ||||
|                 result.Add(new Segment(line, _appearance)); | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         private IEnumerable<string> Partition(Encoding encoding, string text, int width) | ||||
|         { | ||||
|             var lines = new List<string>(); | ||||
|             var line = new StringBuilder(); | ||||
|  | ||||
|             var position = 0; | ||||
|             foreach (var token in text) | ||||
|             { | ||||
|                 if (token == '\n') | ||||
|                 { | ||||
|                     lines.Add(line.ToString()); | ||||
|                     line.Clear(); | ||||
|                     position = 0; | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (position >= width) | ||||
|                 { | ||||
|                     lines.Add(line.ToString()); | ||||
|                     line.Clear(); | ||||
|                     position = 0; | ||||
|                 } | ||||
|  | ||||
|                 line.Append(token); | ||||
|                 position += token.CellLength(encoding); | ||||
|             } | ||||
|  | ||||
|             if (line.Length > 0) | ||||
|             { | ||||
|                 lines.Add(line.ToString()); | ||||
|             } | ||||
|  | ||||
|             // Justify lines | ||||
|             for (var i = 0; i < lines.Count; i++) | ||||
|             { | ||||
|                 if (_justify != Justify.Left && lines[i].CellLength(encoding) < width) | ||||
|                 { | ||||
|                     if (_justify == Justify.Right) | ||||
|                     { | ||||
|                         var diff = width - lines[i].CellLength(encoding); | ||||
|                         lines[i] = new string(' ', diff) + lines[i]; | ||||
|                     } | ||||
|                     else if (_justify == Justify.Center) | ||||
|                     { | ||||
|                         var diff = (width - lines[i].CellLength(encoding)) / 2; | ||||
|                         lines[i] = new string(' ', diff) + lines[i] + new string(' ', diff); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (i < lines.Count - 1) | ||||
|                 { | ||||
|                     lines[i] += "\n"; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return lines; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user