mirror of
				https://github.com/spectreconsole/spectre.console.git
				synced 2025-10-25 15:19:23 +00:00 
			
		
		
		
	Compare commits
	
		
			26 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 8a01b93aca | ||
|  | effdecb1d4 | ||
|  | 4cfe55cc27 | ||
|  | 5b33f80213 | ||
|  | d7bbaf4a85 | ||
|  | 0119364728 | ||
|  | 1d74fb909c | ||
|  | 5d132220ba | ||
|  | a273f74758 | ||
|  | 717931f11c | ||
|  | bcfc495843 | ||
|  | 9aa36c4cf0 | ||
|  | 22d4af4482 | ||
|  | f4d1796e40 | ||
|  | 2dd0eb9f74 | ||
|  | fa85216554 | ||
|  | d475e3b30a | ||
|  | 9637066927 | ||
|  | 0b4321115a | ||
|  | 5cd9ece31a | ||
|  | b0341862cf | ||
|  | 2e7b3d520a | ||
|  | 646f51a628 | ||
|  | a0bd481255 | ||
|  | 6d197c5140 | ||
|  | 108e56c229 | 
							
								
								
									
										1
									
								
								.github/funding.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.github/funding.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | github: patriksvensson | ||||||
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| # `Spectre.Console` | # `Spectre.Console` | ||||||
|  |  | ||||||
| _[](https://www.nuget.org/packages/spectre.console)_ | _[](https://www.nuget.org/packages/spectre.console)_ | ||||||
|  |  | ||||||
| A .NET Standard 2.0 library that makes it easier to create beautiful console applications.   | A .NET Standard 2.0 library that makes it easier to create beautiful console applications.   | ||||||
| It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)  | It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)  | ||||||
| @@ -13,8 +13,9 @@ for Python. | |||||||
| 3. [Usage](#usage)   | 3. [Usage](#usage)   | ||||||
|    3.1. [Using the static API](#using-the-static-api)   |    3.1. [Using the static API](#using-the-static-api)   | ||||||
|    3.2. [Creating a console](#creating-a-console) |    3.2. [Creating a console](#creating-a-console) | ||||||
| 4. [Available styles](#available-styles) | 4. [Running examples](#running-examples) | ||||||
| 5. [Predefined colors](#predefined-colors) | 5. [Available styles](#available-styles) | ||||||
|  | 6. [Predefined colors](#predefined-colors) | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
|  |  | ||||||
| @@ -84,6 +85,42 @@ when manually creating a console, remember that the user's terminal | |||||||
| might not be able to use it, so unless you're creating an IAnsiConsole  | might not be able to use it, so unless you're creating an IAnsiConsole  | ||||||
| for testing, always use `ColorSystemSupport.Detect` and `AnsiSupport.Detect`._ | for testing, always use `ColorSystemSupport.Detect` and `AnsiSupport.Detect`._ | ||||||
|  |  | ||||||
|  | ## Running examples | ||||||
|  |  | ||||||
|  | To see Spectre.Console in action, install the  | ||||||
|  | [dotnet-example](https://github.com/patriksvensson/dotnet-example) | ||||||
|  | global tool. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | > dotnet tool install -g dotnet-example | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Now you can list available examples in this repository: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | > dotnet example | ||||||
|  |  | ||||||
|  | Examples | ||||||
|  |  | ||||||
|  | Colors    Demonstrates how to use colors in the console. | ||||||
|  | Grid      Demonstrates how to render grids in a console. | ||||||
|  | Panel     Demonstrates how to render items in panels. | ||||||
|  | Table     Demonstrates how to render tables in a console. | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | And to run an example: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | > dotnet example table | ||||||
|  | ┌──────────┬──────────┬────────┐ | ||||||
|  | │ Foo      │ Bar      │ Baz    │ | ||||||
|  | ├──────────┼──────────┼────────┤ | ||||||
|  | │ Hello    │ World!   │        │ | ||||||
|  | │ Bounjour │ le       │ monde! │ | ||||||
|  | │ Hej      │ Världen! │        │ | ||||||
|  | └──────────┴──────────┴────────┘ | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ## Available styles | ## Available styles | ||||||
|  |  | ||||||
| _NOTE: Not all styles are supported in every terminal._ | _NOTE: Not all styles are supported in every terminal._ | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								examples/Colors/Colors.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								examples/Colors/Colors.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |     <IsPackable>false</IsPackable> | ||||||
|  |     <Description>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</Description> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										78
									
								
								examples/Colors/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								examples/Colors/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace ColorExample | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             ///////////////////////////////////////////////////////////////// | ||||||
|  |             // 4-BIT | ||||||
|  |             ///////////////////////////////////////////////////////////////// | ||||||
|  |  | ||||||
|  |             AnsiConsole.ResetColors(); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |             AnsiConsole.MarkupLine("[bold underline]4-bit Colors[/]"); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|  |             for (var i = 0; i < 16; i++) | ||||||
|  |             { | ||||||
|  |                 AnsiConsole.Background = Color.FromInt32(i); | ||||||
|  |                 AnsiConsole.Write(string.Format(" {0,-9}", AnsiConsole.Background.ToString())); | ||||||
|  |                 AnsiConsole.ResetColors(); | ||||||
|  |                 if ((i + 1) % 8 == 0) | ||||||
|  |                 { | ||||||
|  |                     AnsiConsole.WriteLine(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ///////////////////////////////////////////////////////////////// | ||||||
|  |             // 8-BIT | ||||||
|  |             ///////////////////////////////////////////////////////////////// | ||||||
|  |  | ||||||
|  |             AnsiConsole.ResetColors(); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |             AnsiConsole.MarkupLine("[bold underline]8-bit Colors[/]"); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|  |             for (var i = 0; i < 16; i++) | ||||||
|  |             { | ||||||
|  |                 for (var j = 0; j < 16; j++) | ||||||
|  |                 { | ||||||
|  |                     var number = i * 16 + j; | ||||||
|  |                     AnsiConsole.Background = Color.FromInt32(number); | ||||||
|  |                     AnsiConsole.Write(string.Format(" {0,-4}", number)); | ||||||
|  |                     AnsiConsole.ResetColors(); | ||||||
|  |                     if ((number + 1) % 16 == 0) | ||||||
|  |                     { | ||||||
|  |                         AnsiConsole.WriteLine(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ///////////////////////////////////////////////////////////////// | ||||||
|  |             // 24-BIT | ||||||
|  |             ///////////////////////////////////////////////////////////////// | ||||||
|  |  | ||||||
|  |             AnsiConsole.ResetColors(); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |             AnsiConsole.MarkupLine("[bold underline]24-bit Colors[/]"); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|  |             var index = 0; | ||||||
|  |             for (var i = 0.0005; i < 1; i += 0.0025) | ||||||
|  |             { | ||||||
|  |                 index++; | ||||||
|  |  | ||||||
|  |                 var color = Utilities.HSL2RGB(i, 0.5, 0.5); | ||||||
|  |                 AnsiConsole.Background = new Color(color.R, color.G, color.B); | ||||||
|  |                 AnsiConsole.Write(" "); | ||||||
|  |  | ||||||
|  |                 if (index % 50 == 0) | ||||||
|  |                 { | ||||||
|  |                     AnsiConsole.WriteLine(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								examples/Colors/Utilities.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								examples/Colors/Utilities.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | using System; | ||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace ColorExample | ||||||
|  | { | ||||||
|  |     public static class Utilities | ||||||
|  |     { | ||||||
|  |         // Borrowed from https://geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm | ||||||
|  |         public static Color HSL2RGB(double h, double sl, double l) | ||||||
|  |         { | ||||||
|  |             double v; | ||||||
|  |             double r, g, b; | ||||||
|  |  | ||||||
|  |             r = l;   // default to gray | ||||||
|  |             g = l; | ||||||
|  |             b = l; | ||||||
|  |             v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl); | ||||||
|  |  | ||||||
|  |             if (v > 0) | ||||||
|  |             { | ||||||
|  |                 double m; | ||||||
|  |                 double sv; | ||||||
|  |                 int sextant; | ||||||
|  |                 double fract, vsf, mid1, mid2; | ||||||
|  |  | ||||||
|  |                 m = l + l - v; | ||||||
|  |                 sv = (v - m) / v; | ||||||
|  |                 h *= 6.0; | ||||||
|  |  | ||||||
|  |                 sextant = (int)h; | ||||||
|  |                 fract = h - sextant; | ||||||
|  |                 vsf = v * sv * fract; | ||||||
|  |                 mid1 = m + vsf; | ||||||
|  |                 mid2 = v - vsf; | ||||||
|  |  | ||||||
|  |                 switch (sextant) | ||||||
|  |                 { | ||||||
|  |                     case 0: | ||||||
|  |                         r = v; | ||||||
|  |                         g = mid1; | ||||||
|  |                         b = m; | ||||||
|  |                         break; | ||||||
|  |                     case 1: | ||||||
|  |                         r = mid2; | ||||||
|  |                         g = v; | ||||||
|  |                         b = m; | ||||||
|  |                         break; | ||||||
|  |                     case 2: | ||||||
|  |                         r = m; | ||||||
|  |                         g = v; | ||||||
|  |                         b = mid1; | ||||||
|  |                         break; | ||||||
|  |                     case 3: | ||||||
|  |                         r = m; | ||||||
|  |                         g = mid2; | ||||||
|  |                         b = v; | ||||||
|  |                         break; | ||||||
|  |                     case 4: | ||||||
|  |                         r = mid1; | ||||||
|  |                         g = m; | ||||||
|  |                         b = v; | ||||||
|  |                         break; | ||||||
|  |                     case 5: | ||||||
|  |                         r = v; | ||||||
|  |                         g = m; | ||||||
|  |                         b = mid2; | ||||||
|  |                         break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new Color( | ||||||
|  |                 Convert.ToByte(r * 255.0f), | ||||||
|  |                 Convert.ToByte(g * 255.0f), | ||||||
|  |                 Convert.ToByte(b * 255.0f)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								examples/Grid/Grid.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								examples/Grid/Grid.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |     <IsPackable>false</IsPackable> | ||||||
|  |     <Description>Demonstrates how to render grids in a console.</Description> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										25
									
								
								examples/Grid/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								examples/Grid/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace GridExample | ||||||
|  | { | ||||||
|  |     public sealed class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |             AnsiConsole.MarkupLine("Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]"); | ||||||
|  |             AnsiConsole.WriteLine(); | ||||||
|  |  | ||||||
|  |             var grid = new Grid(); | ||||||
|  |             grid.AddColumn(new GridColumn { NoWrap = true }); | ||||||
|  |             grid.AddColumn(new GridColumn { NoWrap = true, Width = 2 }); | ||||||
|  |             grid.AddColumn(); | ||||||
|  |             grid.AddRow("Options:", "", ""); | ||||||
|  |             grid.AddRow("  [blue]-h[/], [blue]--help[/]", "", "Show command line help."); | ||||||
|  |             grid.AddRow("  [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "", "The configuration to run for."); | ||||||
|  |             grid.AddRow("  [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "", "Set the [grey]MSBuild[/] verbosity level."); | ||||||
|  |  | ||||||
|  |             AnsiConsole.Render(grid); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,12 +3,12 @@ | |||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <OutputType>Exe</OutputType> |     <OutputType>Exe</OutputType> | ||||||
|     <TargetFramework>netcoreapp3.1</TargetFramework> |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|     <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild> |  | ||||||
|     <IsPackable>false</IsPackable> |     <IsPackable>false</IsPackable> | ||||||
|  |     <Description>Demonstrates how to render items in panels.</Description> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> |     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
| </Project> | </Project> | ||||||
							
								
								
									
										49
									
								
								examples/Panel/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								examples/Panel/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace PanelExample | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             var content = Text.Markup( | ||||||
|  |                 "[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" + | ||||||
|  |                 "So I put a 📦 in a 📦\n\n" + | ||||||
|  |                 "😅"); | ||||||
|  |  | ||||||
|  |             AnsiConsole.Render( | ||||||
|  |                 new Panel( | ||||||
|  |                     new Panel(content) | ||||||
|  |                     { | ||||||
|  |                         Alignment = Justify.Center, | ||||||
|  |                         Border = BorderKind.Rounded | ||||||
|  |                     })); | ||||||
|  |  | ||||||
|  |             // Left adjusted panel with text | ||||||
|  |             AnsiConsole.Render(new Panel( | ||||||
|  |                 new Text("Left adjusted\nLeft")) | ||||||
|  |             { | ||||||
|  |                 Expand = true, | ||||||
|  |                 Alignment = Justify.Left, | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             // Centered ASCII panel with text | ||||||
|  |             AnsiConsole.Render(new Panel( | ||||||
|  |                 new Text("Centered\nCenter")) | ||||||
|  |             { | ||||||
|  |                 Expand = true, | ||||||
|  |                 Alignment = Justify.Center, | ||||||
|  |                 Border = BorderKind.Ascii, | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             // Right adjusted, rounded panel with text | ||||||
|  |             AnsiConsole.Render(new Panel( | ||||||
|  |                 new Text("Right adjusted\nRight")) | ||||||
|  |             { | ||||||
|  |                 Expand = true, | ||||||
|  |                 Alignment = Justify.Right, | ||||||
|  |                 Border = BorderKind.Rounded, | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										90
									
								
								examples/Table/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								examples/Table/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | using Spectre.Console; | ||||||
|  |  | ||||||
|  | namespace TableExample | ||||||
|  | { | ||||||
|  |     class Program | ||||||
|  |     { | ||||||
|  |         static void Main(string[] args) | ||||||
|  |         { | ||||||
|  |             // A simple table§ | ||||||
|  |             RenderSimpleTable(); | ||||||
|  |  | ||||||
|  |             // A big table | ||||||
|  |             RenderBigTable(); | ||||||
|  |  | ||||||
|  |             // A complex table | ||||||
|  |             RenderComplexTable(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void RenderComplexTable() | ||||||
|  |         { | ||||||
|  |             // Create simple table. | ||||||
|  |             var simple = new Table { Border = BorderKind.Rounded }; | ||||||
|  |             simple.AddColumn(new TableColumn("[u]Foo[/]").Centered()); | ||||||
|  |             simple.AddColumn(new TableColumn("[u]Bar[/]")); | ||||||
|  |             simple.AddColumn(new TableColumn("[u]Baz[/]")); | ||||||
|  |             simple.AddRow("Hello", "[red]World![/]", ""); | ||||||
|  |             simple.AddRow("[blue]Bounjour[/]", "[white]le[/]", "[red]monde![/]"); | ||||||
|  |             simple.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); | ||||||
|  |  | ||||||
|  |             // Create other table. | ||||||
|  |             var second = new Table { Border = BorderKind.Square }; | ||||||
|  |             second.AddColumn(new TableColumn("[u]Foo[/]")); | ||||||
|  |             second.AddColumn(new TableColumn("[u]Bar[/]")); | ||||||
|  |             second.AddColumn(new TableColumn("[u]Baz[/]")); | ||||||
|  |             second.AddRow("Hello", "[red]World![/]", ""); | ||||||
|  |             second.AddRow(simple, new Text("Whaaat"), new Text("Lolz")); | ||||||
|  |             second.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); | ||||||
|  |  | ||||||
|  |             var table = new Table { Border = BorderKind.Rounded }; | ||||||
|  |             table.AddColumn(new TableColumn(new Panel("[u]Foo[/]"))); | ||||||
|  |             table.AddColumn(new TableColumn(new Panel("[u]Bar[/]"))); | ||||||
|  |             table.AddColumn(new TableColumn(new Panel("[u]Baz[/]"))); | ||||||
|  |  | ||||||
|  |             // Add some rows | ||||||
|  |             table.AddRow(new Text("Hello").Centered(), new Markup("[red]World![/] 🌍"), Text.Empty); | ||||||
|  |             table.AddRow(second, new Text("Whaaat"), new Text("Lol")); | ||||||
|  |             table.AddRow(new Markup("[blue]Hej[/]").Centered(), new Markup("[yellow]Världen![/]"), Text.Empty); | ||||||
|  |  | ||||||
|  |             AnsiConsole.Render(table); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void RenderSimpleTable() | ||||||
|  |         { | ||||||
|  |             // Create the table. | ||||||
|  |             var table = new Table(); | ||||||
|  |             table.AddColumn(new TableColumn("[u]Foo[/]")); | ||||||
|  |             table.AddColumn(new TableColumn("[u]Bar[/]")); | ||||||
|  |             table.AddColumn(new TableColumn("[u]Baz[/]")); | ||||||
|  |  | ||||||
|  |             // Add some rows | ||||||
|  |             table.AddRow("Hello", "[red]World![/]", ""); | ||||||
|  |             table.AddRow("[blue]Bounjour[/]", "[white]le[/]", "[red]monde![/]"); | ||||||
|  |             table.AddRow("[blue]Hej[/]", "[yellow]Världen![/]", ""); | ||||||
|  |  | ||||||
|  |             AnsiConsole.Render(table); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static void RenderBigTable() | ||||||
|  |         { | ||||||
|  |             // Create the table. | ||||||
|  |             var table = new Table { Border = BorderKind.Rounded }; | ||||||
|  |             table.AddColumn("[red underline]Foo[/]"); | ||||||
|  |             table.AddColumn(new TableColumn("[blue]Bar[/]") { Alignment = Justify.Right, NoWrap = true }); | ||||||
|  |  | ||||||
|  |             // Add some rows | ||||||
|  |             table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍"); | ||||||
|  |             table.AddRow("[yellow]Patrik [green]\"Hello World\"[/] Svensson[/]", "Was [underline]here[/]!"); | ||||||
|  |             table.AddEmptyRow(); | ||||||
|  |             table.AddRow( | ||||||
|  |                 "Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + | ||||||
|  |                 "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " + | ||||||
|  |                 "dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " + | ||||||
|  |                 "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language"); | ||||||
|  |             table.AddEmptyRow(); | ||||||
|  |             table.AddRow("Hej 👋", "[green]Världen[/]"); | ||||||
|  |  | ||||||
|  |             AnsiConsole.Render(table); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								examples/Table/Table.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								examples/Table/Table.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |  | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |     <IsPackable>false</IsPackable> | ||||||
|  |     <Description>Demonstrates how to render tables in a console.</Description> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |  | ||||||
|  | </Project> | ||||||
							
								
								
									
										
											BIN
										
									
								
								gfx/large-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gfx/large-logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 600 KiB | 
							
								
								
									
										
											BIN
										
									
								
								gfx/medium-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gfx/medium-logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								gfx/small-logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gfx/small-logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.5 KiB | 
| @@ -77,4 +77,10 @@ dotnet_diagnostic.CA1032.severity = none | |||||||
| dotnet_diagnostic.CA1826.severity = none | dotnet_diagnostic.CA1826.severity = none | ||||||
|  |  | ||||||
| # RCS1079: Throwing of new NotImplementedException. | # RCS1079: Throwing of new NotImplementedException. | ||||||
| dotnet_diagnostic.RCS1079.severity = warning | dotnet_diagnostic.RCS1079.severity = warning | ||||||
|  |  | ||||||
|  | # RCS1057: Add empty line between declarations. | ||||||
|  | dotnet_diagnostic.RCS1057.severity = none | ||||||
|  |  | ||||||
|  | # IDE0004: Remove Unnecessary Cast | ||||||
|  | dotnet_diagnostic.IDE0004.severity = warning | ||||||
| @@ -18,6 +18,7 @@ | |||||||
|     <Authors>Patrik Svensson</Authors> |     <Authors>Patrik Svensson</Authors> | ||||||
|     <RepositoryType>git</RepositoryType> |     <RepositoryType>git</RepositoryType> | ||||||
|     <RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl> |     <RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl> | ||||||
|  |     <PackageIcon>small-logo.png</PackageIcon> | ||||||
|     <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> |     <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> | ||||||
|     <PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl> |     <PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl> | ||||||
|     <PackageLicenseExpression>MIT</PackageLicenseExpression> |     <PackageLicenseExpression>MIT</PackageLicenseExpression> | ||||||
| @@ -33,7 +34,7 @@ | |||||||
|   <ItemGroup Label="Package References"> |   <ItemGroup Label="Package References"> | ||||||
|     <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" /> |     <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" /> | ||||||
|     <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" /> |     <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8"> |     <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0"> | ||||||
|       <PrivateAssets>all</PrivateAssets> |       <PrivateAssets>all</PrivateAssets> | ||||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|     </PackageReference> |     </PackageReference> | ||||||
|   | |||||||
| @@ -1,8 +0,0 @@ | |||||||
| root = false |  | ||||||
| [*.cs] |  | ||||||
|  |  | ||||||
| # Default severity for all analyzer diagnostics |  | ||||||
| dotnet_analyzer_diagnostic.severity = none |  | ||||||
|  |  | ||||||
| # CS1591: Missing XML comment for publicly visible type or member |  | ||||||
| dotnet_diagnostic.CS1591.severity = none |  | ||||||
| @@ -1,110 +0,0 @@ | |||||||
| using System; |  | ||||||
| using Spectre.Console; |  | ||||||
|  |  | ||||||
| namespace Sample |  | ||||||
| { |  | ||||||
|     public static class Program |  | ||||||
|     { |  | ||||||
|         public static void Main(string[] args) |  | ||||||
|         { |  | ||||||
|             // Use the static API to write some things to the console. |  | ||||||
|             AnsiConsole.Foreground = Color.Chartreuse2; |  | ||||||
|             AnsiConsole.Decoration = Decoration.Underline | Decoration.Bold; |  | ||||||
|             AnsiConsole.WriteLine("Hello World!"); |  | ||||||
|             AnsiConsole.Reset(); |  | ||||||
|             AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities); |  | ||||||
|             AnsiConsole.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height); |  | ||||||
|             AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!"); |  | ||||||
|             AnsiConsole.WriteLine(); |  | ||||||
|  |  | ||||||
|             // We can also use System.ConsoleColor with AnsiConsole. |  | ||||||
|             foreach (ConsoleColor value in Enum.GetValues(typeof(ConsoleColor))) |  | ||||||
|             { |  | ||||||
|                 AnsiConsole.Foreground = value; |  | ||||||
|                 AnsiConsole.WriteLine("ConsoleColor.{0}", value); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // We can get the default console via the static API. |  | ||||||
|             var console = AnsiConsole.Console; |  | ||||||
|  |  | ||||||
|             // Or you can build it yourself the old fashion way. |  | ||||||
|             console = AnsiConsole.Create( |  | ||||||
|                 new AnsiConsoleSettings() |  | ||||||
|                 { |  | ||||||
|                     Ansi = AnsiSupport.Yes, |  | ||||||
|                     ColorSystem = ColorSystemSupport.Standard, |  | ||||||
|                     Out = Console.Out, |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|             // In this case, we will find the closest colors |  | ||||||
|             // and downgrade them to the specified color system. |  | ||||||
|             console.WriteLine(); |  | ||||||
|             console.Foreground = Color.Chartreuse2; |  | ||||||
|             console.Decoration = Decoration.Underline | Decoration.Bold; |  | ||||||
|             console.WriteLine("Hello World!"); |  | ||||||
|             console.ResetColors(); |  | ||||||
|             console.ResetDecoration(); |  | ||||||
|             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( |  | ||||||
|                     "[underline]I[/] heard [underline on blue]you[/] like 📦\n\n\n\n" + |  | ||||||
|                     "So I put a 📦 in a 📦\nin a 📦 in a 📦\n\n" + |  | ||||||
|                     "😅", |  | ||||||
|                     foreground: Color.White), content: 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), |  | ||||||
|                 fit: true, content: Justify.Center)); |  | ||||||
|  |  | ||||||
|             // Right adjusted panel with text |  | ||||||
|             AnsiConsole.Render(new Panel( |  | ||||||
|                 Text.New("Right adjusted\nRight", |  | ||||||
|                     foreground: Color.White), |  | ||||||
|                 fit: true, content: Justify.Right)); |  | ||||||
|  |  | ||||||
|             var table = new Table(); |  | ||||||
|             table.AddColumns("[red underline]Foo[/]", "Bar"); |  | ||||||
|             table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍"); |  | ||||||
|             table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!"); |  | ||||||
|             table.AddRow("Lorem ipsum dolor sit amet, consectetur adipiscing elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language"); |  | ||||||
|             table.AddRow("Hej 👋", "[green]Världen[/]"); |  | ||||||
|             AnsiConsole.Render(table); |  | ||||||
|  |  | ||||||
|             table = new Table(BorderKind.Ascii); |  | ||||||
|             table.AddColumns("[red underline]Foo[/]", "Bar"); |  | ||||||
|             table.AddRow("[blue][underline]Hell[/]o[/]", "World 🌍"); |  | ||||||
|             table.AddRow("[yellow]Patrik [green]\"Lol[/]\" Svensson[/]", "Was [underline]here[/]!"); |  | ||||||
|             table.AddRow("Lorem ipsum dolor sit amet, consectetur [blue]adipiscing[/] elit,sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", "◀ Strange language"); |  | ||||||
|             table.AddRow("Hej 👋", "[green]Världen[/]"); |  | ||||||
|             AnsiConsole.Render(table); |  | ||||||
|  |  | ||||||
|             AnsiConsole.WriteLine(); |  | ||||||
|             AnsiConsole.MarkupLine("  Usage: [grey]dotnet [blue]run[/] [[options] [[[[--] <additional arguments>...]][/]"); |  | ||||||
|             AnsiConsole.WriteLine(); |  | ||||||
|             var grid = new Grid(); |  | ||||||
|             grid.AddColumns(3); |  | ||||||
|             grid.AddRow("  Options", "", ""); |  | ||||||
|             grid.AddRow("    [blue]-h[/], [blue]--help[/]", "   ", "Show command line help."); |  | ||||||
|             grid.AddRow("    [blue]-c[/], [blue]--configuration[/] <CONFIGURATION>", "   ", "The configuration to run for.\nThe default for most projects is [green]Debug[/]."); |  | ||||||
|             grid.AddRow("    [blue]-v[/], [blue]--verbosity[/] <LEVEL>", "   ", "Set the MSBuild verbosity level.\nAllowed values are q[grey][[uiet][/], m[grey][[inimal][/], n[grey][[ormal][/], d[grey][[etailed][/], and diag[grey][[nostic][/]."); |  | ||||||
|             AnsiConsole.Render(grid); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -7,7 +7,7 @@ namespace Spectre.Console.Tests | |||||||
| { | { | ||||||
|     public sealed class PlainConsole : IAnsiConsole, IDisposable |     public sealed class PlainConsole : IAnsiConsole, IDisposable | ||||||
|     { |     { | ||||||
|         public Capabilities Capabilities => throw new NotSupportedException(); |         public Capabilities Capabilities { get; } | ||||||
|         public Encoding Encoding { get; } |         public Encoding Encoding { get; } | ||||||
|  |  | ||||||
|         public int Width { get; } |         public int Width { get; } | ||||||
| @@ -18,14 +18,19 @@ namespace Spectre.Console.Tests | |||||||
|         public Color Background { get; set; } |         public Color Background { get; set; } | ||||||
|  |  | ||||||
|         public StringWriter Writer { get; } |         public StringWriter Writer { get; } | ||||||
|  |         public string RawOutput => Writer.ToString(); | ||||||
|         public string Output => Writer.ToString().TrimEnd('\n'); |         public string Output => Writer.ToString().TrimEnd('\n'); | ||||||
|         public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' }); |         public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' }); | ||||||
|  |  | ||||||
|         public PlainConsole(int width = 80, int height = 9000, Encoding encoding = null) |         public PlainConsole( | ||||||
|  |             int width = 80, int height = 9000, Encoding encoding = null, | ||||||
|  |             bool supportsAnsi = true, ColorSystem colorSystem = ColorSystem.Standard, | ||||||
|  |             bool legacyConsole = false) | ||||||
|         { |         { | ||||||
|  |             Capabilities = new Capabilities(supportsAnsi, colorSystem, legacyConsole); | ||||||
|  |             Encoding = encoding ?? Encoding.UTF8; | ||||||
|             Width = width; |             Width = width; | ||||||
|             Height = height; |             Height = height; | ||||||
|             Encoding = encoding ?? Encoding.UTF8; |  | ||||||
|             Writer = new StringWriter(); |             Writer = new StringWriter(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,16 +2,17 @@ | |||||||
|  |  | ||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <TargetFramework>netcoreapp3.1</TargetFramework> |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |  | ||||||
|     <IsPackable>false</IsPackable> |     <IsPackable>false</IsPackable> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" /> | ||||||
|     <PackageReference Include="Shouldly" Version="4.0.0-beta0002" /> |     <PackageReference Include="Shouldly" Version="4.0.0-beta0002" /> | ||||||
|     <PackageReference Include="xunit" Version="2.4.0" /> |     <PackageReference Include="xunit" Version="2.4.1" /> | ||||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> |     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | ||||||
|     <PackageReference Include="coverlet.collector" Version="1.2.0" /> |       <PrivateAssets>all</PrivateAssets> | ||||||
|  |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|  |     </PackageReference> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|         { |         { | ||||||
|             [Theory] |             [Theory] | ||||||
|             [InlineData("[yellow]Hello[/]", "[93mHello[0m")] |             [InlineData("[yellow]Hello[/]", "[93mHello[0m")] | ||||||
|             [InlineData("[yellow]Hello [italic]World[/]![/]", "[93mHello [0m[3;93mWorld[0m[93m![0m")] |             [InlineData("[yellow]Hello [italic]World[/]![/]", "[93mHello[0m[93m [0m[3;93mWorld[0m[93m![0m")] | ||||||
|             public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected) |             public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected) | ||||||
|             { |             { | ||||||
|                 // Given |                 // Given | ||||||
| @@ -26,7 +26,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             [Theory] |             [Theory] | ||||||
|             [InlineData("[yellow]Hello [[ World[/]", "[93mHello [0m[93m[[0m[93m World[0m")] |             [InlineData("[yellow]Hello [[ World[/]", "[93mHello[0m[93m [0m[93m[[0m[93m [0m[93mWorld[0m")] | ||||||
|             public void Should_Be_Able_To_Escape_Tags(string markup, string expected) |             public void Should_Be_Able_To_Escape_Tags(string markup, string expected) | ||||||
|             { |             { | ||||||
|                 // Given |                 // Given | ||||||
|   | |||||||
| @@ -246,6 +246,38 @@ namespace Spectre.Console.Tests.Unit | |||||||
|  |  | ||||||
|         public sealed class WriteLine |         public sealed class WriteLine | ||||||
|         { |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Reset_Colors_Correctly_After_Line_Break() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 fixture.Console.Background = ConsoleColor.Red; | ||||||
|  |                 fixture.Console.WriteLine("Hello"); | ||||||
|  |                 fixture.Console.Background = ConsoleColor.Green; | ||||||
|  |                 fixture.Console.WriteLine("World"); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 fixture.Output.NormalizeLineEndings() | ||||||
|  |                     .ShouldBe("[101mHello[0m\n[102mWorld[0m\n"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Reset_Colors_Correctly_After_Line_Break_In_Text() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 fixture.Console.Background = ConsoleColor.Red; | ||||||
|  |                 fixture.Console.WriteLine("Hello\nWorld"); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 fixture.Output.NormalizeLineEndings() | ||||||
|  |                     .ShouldBe("[101mHello[0m\n[101mWorld[0m\n"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             [Theory] |             [Theory] | ||||||
|             [InlineData(AnsiSupport.Yes)] |             [InlineData(AnsiSupport.Yes)] | ||||||
|             [InlineData(AnsiSupport.No)] |             [InlineData(AnsiSupport.No)] | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								src/Spectre.Console.Tests/Unit/BorderTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/Spectre.Console.Tests/Unit/BorderTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | using System; | ||||||
|  | using Shouldly; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.Unit | ||||||
|  | { | ||||||
|  |     public sealed class BorderTests | ||||||
|  |     { | ||||||
|  |         public sealed class TheGetBorderMethod | ||||||
|  |         { | ||||||
|  |             [Theory] | ||||||
|  |             [InlineData(BorderKind.None, false, typeof(NoBorder))] | ||||||
|  |             [InlineData(BorderKind.Ascii, false, typeof(AsciiBorder))] | ||||||
|  |             [InlineData(BorderKind.Square, false, typeof(SquareBorder))] | ||||||
|  |             [InlineData(BorderKind.Rounded, false, typeof(RoundedBorder))] | ||||||
|  |             [InlineData(BorderKind.None, true, typeof(NoBorder))] | ||||||
|  |             [InlineData(BorderKind.Ascii, true, typeof(AsciiBorder))] | ||||||
|  |             [InlineData(BorderKind.Square, true, typeof(SquareBorder))] | ||||||
|  |             [InlineData(BorderKind.Rounded, true, typeof(SquareBorder))] | ||||||
|  |             public void Should_Return_Correct_Border_For_Specified_Kind(BorderKind kind, bool safe, Type expected) | ||||||
|  |             { | ||||||
|  |                 // Given, When | ||||||
|  |                 var result = Border.GetBorder(kind, safe); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType(expected); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Unknown_Border_Kind_Is_Specified() | ||||||
|  |             { | ||||||
|  |                 // Given, When | ||||||
|  |                 var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue, false)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>(); | ||||||
|  |                 result.Message.ShouldBe("Unknown border kind"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -78,6 +78,20 @@ namespace Spectre.Console.Tests.Unit | |||||||
|                 // Then |                 // Then | ||||||
|                 result.ShouldBeFalse(); |                 result.ShouldBeFalse(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Shourd_Not_Consider_Black_And_Default_Colors_Equal() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var color1 = Color.Default; | ||||||
|  |                 var color2 = Color.Black; | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = color1.Equals(color2); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeFalse(); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public sealed class TheGetHashCodeMethod |         public sealed class TheGetHashCodeMethod | ||||||
|   | |||||||
| @@ -1,36 +0,0 @@ | |||||||
| using System; |  | ||||||
| using Shouldly; |  | ||||||
| using Spectre.Console.Composition; |  | ||||||
| using Xunit; |  | ||||||
|  |  | ||||||
| namespace Spectre.Console.Tests.Unit.Composition |  | ||||||
| { |  | ||||||
|     public sealed class BorderTests |  | ||||||
|     { |  | ||||||
|         public sealed class TheGetBorderMethod |  | ||||||
|         { |  | ||||||
|             [Theory] |  | ||||||
|             [InlineData(BorderKind.Ascii, typeof(AsciiBorder))] |  | ||||||
|             [InlineData(BorderKind.Square, typeof(SquareBorder))] |  | ||||||
|             public void Should_Return_Correct_Border_For_Specified_Kind(BorderKind kind, Type expected) |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Border.GetBorder(kind); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType(expected); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Unknown_Border_Kind_Is_Specified() |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Record.Exception(() => Border.GetBorder((BorderKind)int.MaxValue)); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<InvalidOperationException>(); |  | ||||||
|                 result.Message.ShouldBe("Unknown border kind"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,96 +0,0 @@ | |||||||
| using System; |  | ||||||
| using Shouldly; |  | ||||||
| using Xunit; |  | ||||||
|  |  | ||||||
| namespace Spectre.Console.Tests.Unit.Composition |  | ||||||
| { |  | ||||||
|     public sealed class GridTests |  | ||||||
|     { |  | ||||||
|         public sealed class TheAddRowMethod |  | ||||||
|         { |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Rows_Are_Null() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var grid = new Grid(); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => grid.AddRow(null)); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<ArgumentNullException>() |  | ||||||
|                     .ParamName.ShouldBe("columns"); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var grid = new Grid(); |  | ||||||
|                 grid.AddColumns(2); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => grid.AddRow("Foo")); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<InvalidOperationException>(); |  | ||||||
|                 result.Message.ShouldBe("The number of row columns are less than the number of grid columns."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var grid = new Grid(); |  | ||||||
|                 grid.AddColumn(); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => grid.AddRow("Foo", "Bar")); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<InvalidOperationException>(); |  | ||||||
|                 result.Message.ShouldBe("The number of row columns are greater than the number of grid columns."); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Grid_With_No_Border_Correctly() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var console = new PlainConsole(width: 80); |  | ||||||
|             var grid = new Grid(); |  | ||||||
|             grid.AddColumns(3); |  | ||||||
|             grid.AddRow("Qux", "Corgi", "Waldo"); |  | ||||||
|             grid.AddRow("Grault", "Garply", "Fred"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             console.Render(grid); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             console.Lines.Count.ShouldBe(2); |  | ||||||
|             console.Lines[0].ShouldBe("Qux     Corgi   Waldo"); |  | ||||||
|             console.Lines[1].ShouldBe("Grault  Garply  Fred "); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Grid() |  | ||||||
|         { |  | ||||||
|             var console = new PlainConsole(width: 120); |  | ||||||
|             var grid = new Grid(); |  | ||||||
|             grid.AddColumns(3); |  | ||||||
|             grid.AddRow("[bold]Options[/]", string.Empty, string.Empty); |  | ||||||
|             grid.AddRow("  [blue]-h[/], [blue]--help[/]", "   ", "Show command line help."); |  | ||||||
|             grid.AddRow("  [blue]-c[/], [blue]--configuration[/]", "   ", "The configuration to run for.\nThe default for most projects is [green]Debug[/]."); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             console.Render(grid); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             console.Lines.Count.ShouldBe(4); |  | ||||||
|             console.Lines[0].ShouldBe("Options                                                         "); |  | ||||||
|             console.Lines[1].ShouldBe("  -h, --help             Show command line help.                "); |  | ||||||
|             console.Lines[2].ShouldBe("  -c, --configuration    The configuration to run for.          "); |  | ||||||
|             console.Lines[3].ShouldBe("                         The default for most projects is Debug."); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,178 +0,0 @@ | |||||||
| using System; |  | ||||||
| using Shouldly; |  | ||||||
| using Xunit; |  | ||||||
|  |  | ||||||
| namespace Spectre.Console.Tests.Unit.Composition |  | ||||||
| { |  | ||||||
|     public sealed class TableTests |  | ||||||
|     { |  | ||||||
|         public sealed class TheAddColumnMethod |  | ||||||
|         { |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Column_Is_Null() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var table = new Table(); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => table.AddColumn(null)); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<ArgumentNullException>() |  | ||||||
|                     .ParamName.ShouldBe("column"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public sealed class TheAddColumnsMethod |  | ||||||
|         { |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Columns_Are_Null() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var table = new Table(); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => table.AddColumns(null)); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<ArgumentNullException>() |  | ||||||
|                     .ParamName.ShouldBe("columns"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public sealed class TheAddRowMethod |  | ||||||
|         { |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Rows_Are_Null() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var table = new Table(); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => table.AddRow(null)); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<ArgumentNullException>() |  | ||||||
|                     .ParamName.ShouldBe("columns"); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var table = new Table(); |  | ||||||
|                 table.AddColumn("Hello"); |  | ||||||
|                 table.AddColumn("World"); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => table.AddRow("Foo")); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<InvalidOperationException>(); |  | ||||||
|                 result.Message.ShouldBe("The number of row columns are less than the number of table columns."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var table = new Table(); |  | ||||||
|                 table.AddColumn("Hello"); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 var result = Record.Exception(() => table.AddRow("Foo", "Bar")); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeOfType<InvalidOperationException>(); |  | ||||||
|                 result.Message.ShouldBe("The number of row columns are greater than the number of table columns."); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Table_Correctly() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var console = new PlainConsole(width: 80); |  | ||||||
|             var table = new Table(); |  | ||||||
|             table.AddColumns("Foo", "Bar", "Baz"); |  | ||||||
|             table.AddRow("Qux", "Corgi", "Waldo"); |  | ||||||
|             table.AddRow("Grault", "Garply", "Fred"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             console.Render(table); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             console.Lines[0].ShouldBe("┌────────┬────────┬───────┐"); |  | ||||||
|             console.Lines[1].ShouldBe("│ Foo    │ Bar    │ Baz   │"); |  | ||||||
|             console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); |  | ||||||
|             console.Lines[3].ShouldBe("│ Qux    │ Corgi  │ Waldo │"); |  | ||||||
|             console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred  │"); |  | ||||||
|             console.Lines[5].ShouldBe("└────────┴────────┴───────┘"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Table_With_Specified_Border_Correctly() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var console = new PlainConsole(width: 80); |  | ||||||
|             var table = new Table(BorderKind.Ascii); |  | ||||||
|             table.AddColumns("Foo", "Bar", "Baz"); |  | ||||||
|             table.AddRow("Qux", "Corgi", "Waldo"); |  | ||||||
|             table.AddRow("Grault", "Garply", "Fred"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             console.Render(table); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             console.Lines[0].ShouldBe("+-------------------------+"); |  | ||||||
|             console.Lines[1].ShouldBe("| Foo    | Bar    | Baz   |"); |  | ||||||
|             console.Lines[2].ShouldBe("|--------+--------+-------|"); |  | ||||||
|             console.Lines[3].ShouldBe("| Qux    | Corgi  | Waldo |"); |  | ||||||
|             console.Lines[4].ShouldBe("| Grault | Garply | Fred  |"); |  | ||||||
|             console.Lines[5].ShouldBe("+-------------------------+"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Table_With_No_Border_Correctly() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var console = new PlainConsole(width: 80); |  | ||||||
|             var table = new Table(BorderKind.None); |  | ||||||
|             table.AddColumns("Foo", "Bar", "Baz"); |  | ||||||
|             table.AddRow("Qux", "Corgi", "Waldo"); |  | ||||||
|             table.AddRow("Grault", "Garply", "Fred"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             console.Render(table); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             console.Lines.Count.ShouldBe(3); |  | ||||||
|             console.Lines[0].ShouldBe("Foo     Bar     Baz  "); |  | ||||||
|             console.Lines[1].ShouldBe("Qux     Corgi   Waldo"); |  | ||||||
|             console.Lines[2].ShouldBe("Grault  Garply  Fred "); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var console = new PlainConsole(width: 80); |  | ||||||
|             var table = new Table(); |  | ||||||
|             table.AddColumns("Foo", "Bar", "Baz"); |  | ||||||
|             table.AddRow("Qux\nQuuux", "Corgi", "Waldo"); |  | ||||||
|             table.AddRow("Grault", "Garply", "Fred"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             console.Render(table); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             console.Lines[0].ShouldBe("┌────────┬────────┬───────┐"); |  | ||||||
|             console.Lines[1].ShouldBe("│ Foo    │ Bar    │ Baz   │"); |  | ||||||
|             console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); |  | ||||||
|             console.Lines[3].ShouldBe("│ Qux    │ Corgi  │ Waldo │"); |  | ||||||
|             console.Lines[4].ShouldBe("│ Quuux  │        │       │"); |  | ||||||
|             console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred  │"); |  | ||||||
|             console.Lines[6].ShouldBe("└────────┴────────┴───────┘"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,77 +0,0 @@ | |||||||
| using Shouldly; |  | ||||||
| using Xunit; |  | ||||||
|  |  | ||||||
| namespace Spectre.Console.Tests.Unit |  | ||||||
| { |  | ||||||
|     public sealed class TextTests |  | ||||||
|     { |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Render_Unstyled_Text_As_Expected() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var fixture = new PlainConsole(width: 80); |  | ||||||
|             var text = Text.New("Hello World"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             fixture.Render(text); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             fixture.Output |  | ||||||
|                 .NormalizeLineEndings() |  | ||||||
|                 .ShouldBe("Hello World"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [Fact] |  | ||||||
|         public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width() |  | ||||||
|         { |  | ||||||
|             // Given |  | ||||||
|             var fixture = new PlainConsole(width: 5); |  | ||||||
|             var text = Text.New("Hello World"); |  | ||||||
|  |  | ||||||
|             // When |  | ||||||
|             fixture.Render(text); |  | ||||||
|  |  | ||||||
|             // Then |  | ||||||
|             fixture.Output |  | ||||||
|                 .NormalizeLineEndings() |  | ||||||
|                 .ShouldBe("Hello\n Worl\nd"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public sealed class TheStylizeMethod |  | ||||||
|         { |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Apply_Style_To_Text() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard); |  | ||||||
|                 var text = Text.New("Hello World"); |  | ||||||
|                 text.Stylize(start: 3, end: 8, new Style(decoration: Decoration.Underline)); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 fixture.Console.Render(text); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 fixture.Output |  | ||||||
|                     .NormalizeLineEndings() |  | ||||||
|                     .ShouldBe("Hel[4mlo Wo[0mrld"); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Apply_Style_To_Text_Which_Spans_Over_Multiple_Lines() |  | ||||||
|             { |  | ||||||
|                 // Given |  | ||||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, width: 5); |  | ||||||
|                 var text = Text.New("Hello World"); |  | ||||||
|                 text.Stylize(start: 3, end: 8, new Style(decoration: Decoration.Underline)); |  | ||||||
|  |  | ||||||
|                 // When |  | ||||||
|                 fixture.Console.Render(text); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 fixture.Output |  | ||||||
|                     .NormalizeLineEndings() |  | ||||||
|                     .ShouldBe("Hel[4mlo[0m\n[4m Wo[0mrl\nd"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										233
									
								
								src/Spectre.Console.Tests/Unit/GridTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/Spectre.Console.Tests/Unit/GridTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | |||||||
|  | using System; | ||||||
|  | using Shouldly; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.Unit | ||||||
|  | { | ||||||
|  |     public sealed class GridTests | ||||||
|  |     { | ||||||
|  |         public sealed class TheAddColumnMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Rows_Are_Not_Empty() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var grid = new Grid(); | ||||||
|  |                 grid.AddColumn(); | ||||||
|  |                 grid.AddRow("Hello World!"); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => grid.AddColumn()); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>() | ||||||
|  |                     .Message.ShouldBe("Cannot add new columns to grid with existing rows."); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public sealed class TheAddRowMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Rows_Are_Null() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var grid = new Grid(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => grid.AddRow(null)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<ArgumentNullException>() | ||||||
|  |                     .ParamName.ShouldBe("columns"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var grid = new Grid(); | ||||||
|  |                 grid.AddColumn(); | ||||||
|  |                 grid.AddColumn(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => grid.AddRow("Foo")); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>(); | ||||||
|  |                 result.Message.ShouldBe("The number of row columns are less than the number of grid columns."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var grid = new Grid(); | ||||||
|  |                 grid.AddColumn(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => grid.AddRow("Foo", "Bar")); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>(); | ||||||
|  |                 result.Message.ShouldBe("The number of row columns are greater than the number of grid columns."); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public sealed class TheAddEmptyRowMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Add_Empty_Row() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var console = new PlainConsole(width: 80); | ||||||
|  |                 var grid = new Grid(); | ||||||
|  |                 grid.AddColumns(2); | ||||||
|  |                 grid.AddRow("Foo", "Bar"); | ||||||
|  |                 grid.AddEmptyRow(); | ||||||
|  |                 grid.AddRow("Qux", "Corgi"); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 console.Render(grid); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 console.Lines.Count.ShouldBe(3); | ||||||
|  |                 console.Lines[0].ShouldBe("Foo  Bar  "); | ||||||
|  |                 console.Lines[1].ShouldBe("          "); | ||||||
|  |                 console.Lines[2].ShouldBe("Qux  Corgi"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Add_Empty_Row_At_The_End() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var console = new PlainConsole(width: 80); | ||||||
|  |                 var grid = new Grid(); | ||||||
|  |                 grid.AddColumns(2); | ||||||
|  |                 grid.AddRow("Foo", "Bar"); | ||||||
|  |                 grid.AddEmptyRow(); | ||||||
|  |                 grid.AddRow("Qux", "Corgi"); | ||||||
|  |                 grid.AddEmptyRow(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 console.Render(grid); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 console.Lines.Count.ShouldBe(4); | ||||||
|  |                 console.Lines[0].ShouldBe("Foo  Bar  "); | ||||||
|  |                 console.Lines[1].ShouldBe("          "); | ||||||
|  |                 console.Lines[2].ShouldBe("Qux  Corgi"); | ||||||
|  |                 console.Lines[3].ShouldBe("          "); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Grid_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var grid = new Grid(); | ||||||
|  |             grid.AddColumn(); | ||||||
|  |             grid.AddColumn(); | ||||||
|  |             grid.AddColumn(); | ||||||
|  |             grid.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             grid.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(grid); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(2); | ||||||
|  |             console.Lines[0].ShouldBe("Qux     Corgi   Waldo"); | ||||||
|  |             console.Lines[1].ShouldBe("Grault  Garply  Fred "); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Grid_Column_Alignment_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var grid = new Grid(); | ||||||
|  |             grid.AddColumn(new GridColumn { Alignment = Justify.Right }); | ||||||
|  |             grid.AddColumn(new GridColumn { Alignment = Justify.Center }); | ||||||
|  |             grid.AddColumn(new GridColumn { Alignment = Justify.Left }); | ||||||
|  |             grid.AddRow("Foo", "Bar", "Baz"); | ||||||
|  |             grid.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             grid.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(grid); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(3); | ||||||
|  |             console.Lines[0].ShouldBe("   Foo   Bar    Baz  "); | ||||||
|  |             console.Lines[1].ShouldBe("   Qux  Corgi   Waldo"); | ||||||
|  |             console.Lines[2].ShouldBe("Grault  Garply  Fred "); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Use_Default_Padding() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var grid = new Grid(); | ||||||
|  |             grid.AddColumns(3); | ||||||
|  |             grid.AddRow("Foo", "Bar", "Baz"); | ||||||
|  |             grid.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             grid.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(grid); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(3); | ||||||
|  |             console.Lines[0].ShouldBe("Foo     Bar     Baz  "); | ||||||
|  |             console.Lines[1].ShouldBe("Qux     Corgi   Waldo"); | ||||||
|  |             console.Lines[2].ShouldBe("Grault  Garply  Fred "); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Explicit_Grid_Column_Padding_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var grid = new Grid(); | ||||||
|  |             grid.AddColumn(new GridColumn { Padding = new Padding(3, 0) }); | ||||||
|  |             grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) }); | ||||||
|  |             grid.AddColumn(new GridColumn { Padding = new Padding(0, 3) }); | ||||||
|  |             grid.AddRow("Foo", "Bar", "Baz"); | ||||||
|  |             grid.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             grid.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(grid); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(3); | ||||||
|  |             console.Lines[0].ShouldBe("   Foo   Bar   Baz     "); | ||||||
|  |             console.Lines[1].ShouldBe("   Qux   Corgi Waldo   "); | ||||||
|  |             console.Lines[2].ShouldBe("   GraultGarplyFred    "); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Grid() | ||||||
|  |         { | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var grid = new Grid(); | ||||||
|  |             grid.AddColumn(new GridColumn { NoWrap = true }); | ||||||
|  |             grid.AddColumn(new GridColumn { Padding = new Padding(2, 0) }); | ||||||
|  |             grid.AddRow("[bold]Options[/]", string.Empty); | ||||||
|  |             grid.AddRow("  [blue]-h[/], [blue]--help[/]", "Show command line help."); | ||||||
|  |             grid.AddRow("  [blue]-c[/], [blue]--configuration[/]", "The configuration to run for.\nThe default for most projects is [green]Debug[/]."); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(grid); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(4); | ||||||
|  |             console.Lines[0].ShouldBe("Options                                                         "); | ||||||
|  |             console.Lines[1].ShouldBe("  -h, --help             Show command line help.                "); | ||||||
|  |             console.Lines[2].ShouldBe("  -c, --configuration    The configuration to run for.          "); | ||||||
|  |             console.Lines[3].ShouldBe("                         The default for most projects is Debug."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -12,7 +12,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             var console = new PlainConsole(width: 80); |             var console = new PlainConsole(width: 80); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(Text.New("Hello World"))); |             console.Render(new Panel(new Text("Hello World"))); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(3); |             console.Lines.Count.ShouldBe(3); | ||||||
| @@ -21,6 +21,25 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             console.Lines[2].ShouldBe("└─────────────┘"); |             console.Lines[2].ShouldBe("└─────────────┘"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Panel_With_Padding() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  | 
 | ||||||
|  |             // When | ||||||
|  |             console.Render(new Panel(new Text("Hello World")) | ||||||
|  |             { | ||||||
|  |                 Padding = new Padding(3, 5), | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(3); | ||||||
|  |             console.Lines[0].ShouldBe("┌───────────────────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│   Hello World     │"); | ||||||
|  |             console.Lines[2].ShouldBe("└───────────────────┘"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void Should_Render_Panel_With_Unicode_Correctly() |         public void Should_Render_Panel_With_Unicode_Correctly() | ||||||
|         { |         { | ||||||
| @@ -28,7 +47,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             var console = new PlainConsole(width: 80); |             var console = new PlainConsole(width: 80); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(Text.New(" \n💩\n "))); |             console.Render(new Panel(new Text(" \n💩\n "))); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(5); |             console.Lines.Count.ShouldBe(5); | ||||||
| @@ -46,7 +65,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             var console = new PlainConsole(width: 80); |             var console = new PlainConsole(width: 80); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(Text.New("Hello World\nFoo Bar"))); |             console.Render(new Panel(new Text("Hello World\nFoo Bar"))); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(4); |             console.Lines.Count.ShouldBe(4); | ||||||
| @@ -62,8 +81,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             // Given |             // Given | ||||||
|             var console = new PlainConsole(width: 80); |             var console = new PlainConsole(width: 80); | ||||||
|             var text = new Panel( |             var text = new Panel( | ||||||
|                 Text.New("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"), |                 Text.Markup("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦")); | ||||||
|                 content: Justify.Center); |  | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(text); |             console.Render(text); | ||||||
| @@ -71,7 +89,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(7); |             console.Lines.Count.ShouldBe(7); | ||||||
|             console.Lines[0].ShouldBe("┌───────────────────────┐"); |             console.Lines[0].ShouldBe("┌───────────────────────┐"); | ||||||
|             console.Lines[1].ShouldBe("│  I heard you like 📦  │"); |             console.Lines[1].ShouldBe("│ I heard you like 📦   │"); | ||||||
|             console.Lines[2].ShouldBe("│                       │"); |             console.Lines[2].ShouldBe("│                       │"); | ||||||
|             console.Lines[3].ShouldBe("│                       │"); |             console.Lines[3].ShouldBe("│                       │"); | ||||||
|             console.Lines[4].ShouldBe("│                       │"); |             console.Lines[4].ShouldBe("│                       │"); | ||||||
| @@ -80,19 +98,23 @@ namespace Spectre.Console.Tests.Unit | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         [Fact] |         [Fact] | ||||||
|         public void Should_Fit_Panel_To_Parent_If_Enabled() |         public void Should_Expand_Panel_If_Enabled() | ||||||
|         { |         { | ||||||
|             // Given |             // Given | ||||||
|             var console = new PlainConsole(width: 25); |             var console = new PlainConsole(width: 80); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(Text.New("Hello World"), fit: true)); |             console.Render(new Panel(new Text("Hello World")) | ||||||
|  |             { | ||||||
|  |                 Expand = true, | ||||||
|  |             }); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(3); |             console.Lines.Count.ShouldBe(3); | ||||||
|             console.Lines[0].ShouldBe("┌───────────────────────┐"); |             console.Lines[0].Length.ShouldBe(80); | ||||||
|             console.Lines[1].ShouldBe("│ Hello World           │"); |             console.Lines[0].ShouldBe("┌──────────────────────────────────────────────────────────────────────────────┐"); | ||||||
|             console.Lines[2].ShouldBe("└───────────────────────┘"); |             console.Lines[1].ShouldBe("│ Hello World                                                                  │"); | ||||||
|  |             console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         [Fact] |         [Fact] | ||||||
| @@ -102,7 +124,11 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             var console = new PlainConsole(width: 25); |             var console = new PlainConsole(width: 25); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Right)); |             console.Render( | ||||||
|  |                 new Panel(new Text("Hello World").RightAligned()) | ||||||
|  |                 { | ||||||
|  |                     Expand = true, | ||||||
|  |                 }); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(3); |             console.Lines.Count.ShouldBe(3); | ||||||
| @@ -118,7 +144,11 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             var console = new PlainConsole(width: 25); |             var console = new PlainConsole(width: 25); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Center)); |             console.Render( | ||||||
|  |                 new Panel(new Text("Hello World").Centered()) | ||||||
|  |                 { | ||||||
|  |                     Expand = true, | ||||||
|  |                 }); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(3); |             console.Lines.Count.ShouldBe(3); | ||||||
| @@ -134,7 +164,7 @@ namespace Spectre.Console.Tests.Unit | |||||||
|             var console = new PlainConsole(width: 80); |             var console = new PlainConsole(width: 80); | ||||||
| 
 | 
 | ||||||
|             // When |             // When | ||||||
|             console.Render(new Panel(new Panel(Text.New("Hello World")))); |             console.Render(new Panel(new Panel(new Text("Hello World")))); | ||||||
| 
 | 
 | ||||||
|             // Then |             // Then | ||||||
|             console.Lines.Count.ShouldBe(5); |             console.Lines.Count.ShouldBe(5); | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| using Shouldly; | using Shouldly; | ||||||
| using Spectre.Console.Composition; | using Spectre.Console.Rendering; | ||||||
| using Xunit; | using Xunit; | ||||||
| 
 | 
 | ||||||
| namespace Spectre.Console.Tests.Unit | namespace Spectre.Console.Tests.Unit | ||||||
| @@ -39,14 +39,18 @@ namespace Spectre.Console.Tests.Unit | |||||||
|  |  | ||||||
|             [Theory] |             [Theory] | ||||||
|             [InlineData("bold", Decoration.Bold)] |             [InlineData("bold", Decoration.Bold)] | ||||||
|  |             [InlineData("b", Decoration.Bold)] | ||||||
|             [InlineData("dim", Decoration.Dim)] |             [InlineData("dim", Decoration.Dim)] | ||||||
|  |             [InlineData("i", Decoration.Italic)] | ||||||
|             [InlineData("italic", Decoration.Italic)] |             [InlineData("italic", Decoration.Italic)] | ||||||
|             [InlineData("underline", Decoration.Underline)] |             [InlineData("underline", Decoration.Underline)] | ||||||
|  |             [InlineData("u", Decoration.Underline)] | ||||||
|             [InlineData("invert", Decoration.Invert)] |             [InlineData("invert", Decoration.Invert)] | ||||||
|             [InlineData("conceal", Decoration.Conceal)] |             [InlineData("conceal", Decoration.Conceal)] | ||||||
|             [InlineData("slowblink", Decoration.SlowBlink)] |             [InlineData("slowblink", Decoration.SlowBlink)] | ||||||
|             [InlineData("rapidblink", Decoration.RapidBlink)] |             [InlineData("rapidblink", Decoration.RapidBlink)] | ||||||
|             [InlineData("strikethrough", Decoration.Strikethrough)] |             [InlineData("strikethrough", Decoration.Strikethrough)] | ||||||
|  |             [InlineData("s", Decoration.Strikethrough)] | ||||||
|             public void Should_Parse_Decoration(string text, Decoration decoration) |             public void Should_Parse_Decoration(string text, Decoration decoration) | ||||||
|             { |             { | ||||||
|                 // Given, When |                 // Given, When | ||||||
| @@ -126,108 +130,83 @@ namespace Spectre.Console.Tests.Unit | |||||||
|                 result.ShouldBeOfType<InvalidOperationException>(); |                 result.ShouldBeOfType<InvalidOperationException>(); | ||||||
|                 result.Message.ShouldBe("Could not find color 'lol'."); |                 result.Message.ShouldBe("Could not find color 'lol'."); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             [Theory] | ||||||
|  |             [InlineData("#FF0000 on #0000FF")] | ||||||
|  |             [InlineData("#F00 on #00F")] | ||||||
|  |             public void Should_Parse_Hex_Colors_Correctly(string style) | ||||||
|  |             { | ||||||
|  |                 // Given, When | ||||||
|  |                 var result = Style.Parse(style); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.Foreground.ShouldBe(Color.Red); | ||||||
|  |                 result.Background.ShouldBe(Color.Blue); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Theory] | ||||||
|  |             [InlineData("#", "Invalid hex color '#'.")] | ||||||
|  |             [InlineData("#FF00FF00FF", "Invalid hex color '#FF00FF00FF'.")] | ||||||
|  |             [InlineData("#FOO", "Invalid hex color '#FOO'. Could not find any recognizable digits.")] | ||||||
|  |             public void Should_Return_Error_If_Hex_Color_Is_Invalid(string style, string expected) | ||||||
|  |             { | ||||||
|  |                 // Given, When | ||||||
|  |                 var result = Record.Exception(() => Style.Parse(style)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldNotBeNull(); | ||||||
|  |                 result.Message.ShouldBe(expected); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Theory] | ||||||
|  |             [InlineData("rgb(255,0,0) on rgb(0,0,255)")] | ||||||
|  |             public void Should_Parse_Rgb_Colors_Correctly(string style) | ||||||
|  |             { | ||||||
|  |                 // Given, When | ||||||
|  |                 var result = Style.Parse(style); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.Foreground.ShouldBe(Color.Red); | ||||||
|  |                 result.Background.ShouldBe(Color.Blue); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Theory] | ||||||
|  |             [InlineData("rgb()", "Invalid RGB color 'rgb()'.")] | ||||||
|  |             [InlineData("rgb(", "Invalid RGB color 'rgb('.")] | ||||||
|  |             [InlineData("rgb(255)", "Invalid RGB color 'rgb(255)'.")] | ||||||
|  |             [InlineData("rgb(255,255)", "Invalid RGB color 'rgb(255,255)'.")] | ||||||
|  |             [InlineData("rgb(255,255,255", "Invalid RGB color 'rgb(255,255,255'.")] | ||||||
|  |             [InlineData("rgb(A,B,C)", "Invalid RGB color 'rgb(A,B,C)'. Input string was not in a correct format.")] | ||||||
|  |             public void Should_Return_Error_If_Rgb_Color_Is_Invalid(string style, string expected) | ||||||
|  |             { | ||||||
|  |                 // Given, When | ||||||
|  |                 var result = Record.Exception(() => Style.Parse(style)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldNotBeNull(); | ||||||
|  |                 result.Message.ShouldBe(expected); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public sealed class TheTryParseMethod |         public sealed class TheTryParseMethod | ||||||
|         { |         { | ||||||
|             [Fact] |             [Fact] | ||||||
|             public void Default_Keyword_Should_Return_Default_Style() |             public void Should_Return_True_If_Parsing_Succeeded() | ||||||
|             { |             { | ||||||
|                 // Given, When |                 // Given, When | ||||||
|                 var result = Style.TryParse("default", out var style); |                 var result = Style.TryParse("bold", out var style); | ||||||
|  |  | ||||||
|                 // Then |                 // Then | ||||||
|                 result.ShouldBeTrue(); |                 result.ShouldBeTrue(); | ||||||
|                 style.ShouldNotBeNull(); |                 style.ShouldNotBeNull(); | ||||||
|                 style.Foreground.ShouldBe(Color.Default); |                 style.Decoration.ShouldBe(Decoration.Bold); | ||||||
|                 style.Background.ShouldBe(Color.Default); |  | ||||||
|                 style.Decoration.ShouldBe(Decoration.None); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Theory] |  | ||||||
|             [InlineData("bold", Decoration.Bold)] |  | ||||||
|             [InlineData("dim", Decoration.Dim)] |  | ||||||
|             [InlineData("italic", Decoration.Italic)] |  | ||||||
|             [InlineData("underline", Decoration.Underline)] |  | ||||||
|             [InlineData("invert", Decoration.Invert)] |  | ||||||
|             [InlineData("conceal", Decoration.Conceal)] |  | ||||||
|             [InlineData("slowblink", Decoration.SlowBlink)] |  | ||||||
|             [InlineData("rapidblink", Decoration.RapidBlink)] |  | ||||||
|             [InlineData("strikethrough", Decoration.Strikethrough)] |  | ||||||
|             public void Should_Parse_Decoration(string text, Decoration decoration) |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Style.TryParse(text, out var style); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeTrue(); |  | ||||||
|                 style.ShouldNotBeNull(); |  | ||||||
|                 style.Decoration.ShouldBe(decoration); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             [Fact] |             [Fact] | ||||||
|             public void Should_Parse_Text_And_Decoration() |             public void Should_Return_False_If_Parsing_Failed() | ||||||
|             { |             { | ||||||
|                 // Given, When |                 // Given, When | ||||||
|                 var result = Style.TryParse("bold underline blue on green", out var style); |                 var result = Style.TryParse("lol", out _); | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeTrue(); |  | ||||||
|                 style.ShouldNotBeNull(); |  | ||||||
|                 style.Decoration.ShouldBe(Decoration.Bold | Decoration.Underline); |  | ||||||
|                 style.Foreground.ShouldBe(Color.Blue); |  | ||||||
|                 style.Background.ShouldBe(Color.Green); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Parse_Background_If_Foreground_Is_Set_To_Default() |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Style.TryParse("default on green", out var style); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeTrue(); |  | ||||||
|                 style.ShouldNotBeNull(); |  | ||||||
|                 style.Decoration.ShouldBe(Decoration.None); |  | ||||||
|                 style.Foreground.ShouldBe(Color.Default); |  | ||||||
|                 style.Background.ShouldBe(Color.Green); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Foreground_Is_Set_Twice() |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Style.TryParse("green yellow", out _); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeFalse(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Background_Is_Set_Twice() |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Style.TryParse("green on blue yellow", out _); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeFalse(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Color_Name_Could_Not_Be_Found() |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Style.TryParse("bold lol", out _); |  | ||||||
|  |  | ||||||
|                 // Then |  | ||||||
|                 result.ShouldBeFalse(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             [Fact] |  | ||||||
|             public void Should_Throw_If_Background_Color_Name_Could_Not_Be_Found() |  | ||||||
|             { |  | ||||||
|                 // Given, When |  | ||||||
|                 var result = Style.TryParse("blue on lol", out _); |  | ||||||
|  |  | ||||||
|                 // Then |                 // Then | ||||||
|                 result.ShouldBeFalse(); |                 result.ShouldBeFalse(); | ||||||
|   | |||||||
							
								
								
									
										388
									
								
								src/Spectre.Console.Tests/Unit/TableTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										388
									
								
								src/Spectre.Console.Tests/Unit/TableTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,388 @@ | |||||||
|  | using System; | ||||||
|  | using Shouldly; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.Unit | ||||||
|  | { | ||||||
|  |     public sealed class TableTests | ||||||
|  |     { | ||||||
|  |         public sealed class TheAddColumnMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Column_Is_Null() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var table = new Table(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => table.AddColumn((string)null)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<ArgumentNullException>() | ||||||
|  |                     .ParamName.ShouldBe("column"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Rows_Are_Not_Empty() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var grid = new Table(); | ||||||
|  |                 grid.AddColumn("Foo"); | ||||||
|  |                 grid.AddRow("Hello World"); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => grid.AddColumn("Bar")); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>() | ||||||
|  |                     .Message.ShouldBe("Cannot add new columns to table with existing rows."); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public sealed class TheAddColumnsMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Columns_Are_Null() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var table = new Table(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => table.AddColumns((string[])null)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<ArgumentNullException>() | ||||||
|  |                     .ParamName.ShouldBe("columns"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public sealed class TheAddRowMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_String_Rows_Are_Null() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var table = new Table(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => table.AddRow((string[])null)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<ArgumentNullException>() | ||||||
|  |                     .ParamName.ShouldBe("columns"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Renderable_Rows_Are_Null() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var table = new Table(); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => table.AddRow(null)); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<ArgumentNullException>() | ||||||
|  |                     .ParamName.ShouldBe("columns"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Row_Columns_Is_Less_Than_Number_Of_Columns() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var table = new Table(); | ||||||
|  |                 table.AddColumn("Hello"); | ||||||
|  |                 table.AddColumn("World"); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => table.AddRow("Foo")); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>(); | ||||||
|  |                 result.Message.ShouldBe("The number of row columns are less than the number of table columns."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Throw_If_Row_Columns_Are_Greater_Than_Number_Of_Columns() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var table = new Table(); | ||||||
|  |                 table.AddColumn("Hello"); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 var result = Record.Exception(() => table.AddRow("Foo", "Bar")); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 result.ShouldBeOfType<InvalidOperationException>(); | ||||||
|  |                 result.Message.ShouldBe("The number of row columns are greater than the number of table columns."); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public sealed class TheAddEmptyRowMethod | ||||||
|  |         { | ||||||
|  |             [Fact] | ||||||
|  |             public void Should_Render_Table_Correctly() | ||||||
|  |             { | ||||||
|  |                 // Given | ||||||
|  |                 var console = new PlainConsole(width: 80); | ||||||
|  |                 var table = new Table(); | ||||||
|  |                 table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |                 table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |                 table.AddEmptyRow(); | ||||||
|  |                 table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |                 // When | ||||||
|  |                 console.Render(table); | ||||||
|  |  | ||||||
|  |                 // Then | ||||||
|  |                 console.Lines.Count.ShouldBe(7); | ||||||
|  |                 console.Lines[0].ShouldBe("┌────────┬────────┬───────┐"); | ||||||
|  |                 console.Lines[1].ShouldBe("│ Foo    │ Bar    │ Baz   │"); | ||||||
|  |                 console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); | ||||||
|  |                 console.Lines[3].ShouldBe("│ Qux    │ Corgi  │ Waldo │"); | ||||||
|  |                 console.Lines[4].ShouldBe("│        │        │       │"); | ||||||
|  |                 console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred  │"); | ||||||
|  |                 console.Lines[6].ShouldBe("└────────┴────────┴───────┘"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table(); | ||||||
|  |             table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |             table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(6); | ||||||
|  |             console.Lines[0].ShouldBe("┌────────┬────────┬───────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo    │ Bar    │ Baz   │"); | ||||||
|  |             console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); | ||||||
|  |             console.Lines[3].ShouldBe("│ Qux    │ Corgi  │ Waldo │"); | ||||||
|  |             console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred  │"); | ||||||
|  |             console.Lines[5].ShouldBe("└────────┴────────┴───────┘"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_Nested_In_Panels_Correctly() | ||||||
|  |         { | ||||||
|  |             // A simple table | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table() { Border = BorderKind.Rounded }; | ||||||
|  |             table.AddColumn("Foo"); | ||||||
|  |             table.AddColumn("Bar"); | ||||||
|  |             table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Right }); | ||||||
|  |             table.AddRow("Qux\nQuuuuuux", "[blue]Corgi[/]", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // Render a table in some panels. | ||||||
|  |             console.Render(new Panel(new Panel(table) | ||||||
|  |             { | ||||||
|  |                 Border = BorderKind.Ascii, | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(11); | ||||||
|  |             console.Lines[00].ShouldBe("┌───────────────────────────────────┐"); | ||||||
|  |             console.Lines[01].ShouldBe("│ +-------------------------------+ │"); | ||||||
|  |             console.Lines[02].ShouldBe("│ | ╭──────────┬────────┬───────╮ | │"); | ||||||
|  |             console.Lines[03].ShouldBe("│ | │ Foo      │ Bar    │   Baz │ | │"); | ||||||
|  |             console.Lines[04].ShouldBe("│ | ├──────────┼────────┼───────┤ | │"); | ||||||
|  |             console.Lines[05].ShouldBe("│ | │ Qux      │ Corgi  │ Waldo │ | │"); | ||||||
|  |             console.Lines[06].ShouldBe("│ | │ Quuuuuux │        │       │ | │"); | ||||||
|  |             console.Lines[07].ShouldBe("│ | │ Grault   │ Garply │  Fred │ | │"); | ||||||
|  |             console.Lines[08].ShouldBe("│ | ╰──────────┴────────┴───────╯ | │"); | ||||||
|  |             console.Lines[09].ShouldBe("│ +-------------------------------+ │"); | ||||||
|  |             console.Lines[10].ShouldBe("└───────────────────────────────────┘"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_With_Column_Justification_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table(); | ||||||
|  |             table.AddColumn(new TableColumn("Foo") { Alignment = Justify.Left }); | ||||||
|  |             table.AddColumn(new TableColumn("Bar") { Alignment = Justify.Right }); | ||||||
|  |             table.AddColumn(new TableColumn("Baz") { Alignment = Justify.Center }); | ||||||
|  |             table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Lorem ipsum dolor sit amet"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(6); | ||||||
|  |             console.Lines[0].ShouldBe("┌────────┬────────┬────────────────────────────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo    │    Bar │            Baz             │"); | ||||||
|  |             console.Lines[2].ShouldBe("├────────┼────────┼────────────────────────────┤"); | ||||||
|  |             console.Lines[3].ShouldBe("│ Qux    │  Corgi │           Waldo            │"); | ||||||
|  |             console.Lines[4].ShouldBe("│ Grault │ Garply │ Lorem ipsum dolor sit amet │"); | ||||||
|  |             console.Lines[5].ShouldBe("└────────┴────────┴────────────────────────────┘"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Expand_Table_To_Available_Space_If_Specified() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table() { Expand = true }; | ||||||
|  |             table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |             table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(6); | ||||||
|  |             console.Lines[0].Length.ShouldBe(80); | ||||||
|  |             console.Lines[0].ShouldBe("┌───────────────────────────┬───────────────────────────┬──────────────────────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo                       │ Bar                       │ Baz                  │"); | ||||||
|  |             console.Lines[2].ShouldBe("├───────────────────────────┼───────────────────────────┼──────────────────────┤"); | ||||||
|  |             console.Lines[3].ShouldBe("│ Qux                       │ Corgi                     │ Waldo                │"); | ||||||
|  |             console.Lines[4].ShouldBe("│ Grault                    │ Garply                    │ Fred                 │"); | ||||||
|  |             console.Lines[5].ShouldBe("└───────────────────────────┴───────────────────────────┴──────────────────────┘"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_With_Ascii_Border_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table { Border = BorderKind.Ascii }; | ||||||
|  |             table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |             table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(6); | ||||||
|  |             console.Lines[0].ShouldBe("+-------------------------+"); | ||||||
|  |             console.Lines[1].ShouldBe("| Foo    | Bar    | Baz   |"); | ||||||
|  |             console.Lines[2].ShouldBe("|--------+--------+-------|"); | ||||||
|  |             console.Lines[3].ShouldBe("| Qux    | Corgi  | Waldo |"); | ||||||
|  |             console.Lines[4].ShouldBe("| Grault | Garply | Fred  |"); | ||||||
|  |             console.Lines[5].ShouldBe("+-------------------------+"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_With_Rounded_Border_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table { Border = BorderKind.Rounded }; | ||||||
|  |             table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |             table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(6); | ||||||
|  |             console.Lines[0].ShouldBe("╭────────┬────────┬───────╮"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo    │ Bar    │ Baz   │"); | ||||||
|  |             console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); | ||||||
|  |             console.Lines[3].ShouldBe("│ Qux    │ Corgi  │ Waldo │"); | ||||||
|  |             console.Lines[4].ShouldBe("│ Grault │ Garply │ Fred  │"); | ||||||
|  |             console.Lines[5].ShouldBe("╰────────┴────────┴───────╯"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_With_No_Border_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table { Border = BorderKind.None }; | ||||||
|  |             table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |             table.AddRow("Qux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(3); | ||||||
|  |             console.Lines[0].ShouldBe("Foo    Bar    Baz  "); | ||||||
|  |             console.Lines[1].ShouldBe("Qux    Corgi  Waldo"); | ||||||
|  |             console.Lines[2].ShouldBe("Grault Garply Fred "); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_With_Multiple_Rows_In_Cell_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table(); | ||||||
|  |             table.AddColumns("Foo", "Bar", "Baz"); | ||||||
|  |             table.AddRow("Qux\nQuuux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(7); | ||||||
|  |             console.Lines[0].ShouldBe("┌────────┬────────┬───────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo    │ Bar    │ Baz   │"); | ||||||
|  |             console.Lines[2].ShouldBe("├────────┼────────┼───────┤"); | ||||||
|  |             console.Lines[3].ShouldBe("│ Qux    │ Corgi  │ Waldo │"); | ||||||
|  |             console.Lines[4].ShouldBe("│ Quuux  │        │       │"); | ||||||
|  |             console.Lines[5].ShouldBe("│ Grault │ Garply │ Fred  │"); | ||||||
|  |             console.Lines[6].ShouldBe("└────────┴────────┴───────┘"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_With_Cell_Padding_Correctly() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table(); | ||||||
|  |             table.AddColumns("Foo", "Bar"); | ||||||
|  |             table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) }); | ||||||
|  |             table.AddRow("Qux\nQuuux", "Corgi", "Waldo"); | ||||||
|  |             table.AddRow("Grault", "Garply", "Fred"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(7); | ||||||
|  |             console.Lines[0].ShouldBe("┌────────┬────────┬──────────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo    │ Bar    │   Baz    │"); | ||||||
|  |             console.Lines[2].ShouldBe("├────────┼────────┼──────────┤"); | ||||||
|  |             console.Lines[3].ShouldBe("│ Qux    │ Corgi  │   Waldo  │"); | ||||||
|  |             console.Lines[4].ShouldBe("│ Quuux  │        │          │"); | ||||||
|  |             console.Lines[5].ShouldBe("│ Grault │ Garply │   Fred   │"); | ||||||
|  |             console.Lines[6].ShouldBe("└────────┴────────┴──────────┘"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Table_Without_Footer_If_No_Rows_Are_Added() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var console = new PlainConsole(width: 80); | ||||||
|  |             var table = new Table(); | ||||||
|  |             table.AddColumns("Foo", "Bar"); | ||||||
|  |             table.AddColumn(new TableColumn("Baz") { Padding = new Padding(3, 2) }); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             console.Render(table); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             console.Lines.Count.ShouldBe(3); | ||||||
|  |             console.Lines[0].ShouldBe("┌─────┬─────┬────────┐"); | ||||||
|  |             console.Lines[1].ShouldBe("│ Foo │ Bar │   Baz  │"); | ||||||
|  |             console.Lines[2].ShouldBe("└─────┴─────┴────────┘"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										81
									
								
								src/Spectre.Console.Tests/Unit/TextTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/Spectre.Console.Tests/Unit/TextTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | using System.Text; | ||||||
|  | using Shouldly; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  | using Xunit; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Tests.Unit | ||||||
|  | { | ||||||
|  |     public sealed class TextTests | ||||||
|  |     { | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Consider_The_Longest_Word_As_Minimum_Width() | ||||||
|  |         { | ||||||
|  |             var text = new Text("Foo Bar Baz\nQux\nLol mobile"); | ||||||
|  |  | ||||||
|  |             var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||||
|  |  | ||||||
|  |             result.Min.ShouldBe(6); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Consider_The_Longest_Line_As_Maximum_Width() | ||||||
|  |         { | ||||||
|  |             var text = new Text("Foo Bar Baz\nQux\nLol mobile"); | ||||||
|  |  | ||||||
|  |             var result = ((IRenderable)text).Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||||
|  |  | ||||||
|  |             result.Max.ShouldBe(11); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Fact] | ||||||
|  |         public void Should_Render_Unstyled_Text_As_Expected() | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var fixture = new PlainConsole(width: 80); | ||||||
|  |             var text = new Text("Hello World"); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             fixture.Render(text); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             fixture.Output | ||||||
|  |                 .NormalizeLineEndings() | ||||||
|  |                 .ShouldBe("Hello World"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Theory] | ||||||
|  |         [InlineData("Hello\n\nWorld\n\n")] | ||||||
|  |         [InlineData("Hello\r\n\r\nWorld\r\n\r\n")] | ||||||
|  |         public void Should_Write_Line_Breaks(string input) | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var fixture = new PlainConsole(width: 5); | ||||||
|  |             var text = new Text(input); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             fixture.Render(text); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             fixture.RawOutput.ShouldBe("Hello\n\nWorld\n\n"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [Theory] | ||||||
|  |         [InlineData(5, "Hello World", "Hello\nWorld")] | ||||||
|  |         [InlineData(10, "Hello Sweet Nice World", "Hello \nSweet Nice\nWorld")] | ||||||
|  |         public void Should_Split_Unstyled_Text_To_New_Lines_If_Width_Exceeds_Console_Width( | ||||||
|  |             int width, string input, string expected) | ||||||
|  |         { | ||||||
|  |             // Given | ||||||
|  |             var fixture = new PlainConsole(width); | ||||||
|  |             var text = new Text(input); | ||||||
|  |  | ||||||
|  |             // When | ||||||
|  |             fixture.Render(text); | ||||||
|  |  | ||||||
|  |             // Then | ||||||
|  |             fixture.Output | ||||||
|  |                 .NormalizeLineEndings() | ||||||
|  |                 .ShouldBe(expected); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "Spectre. | |||||||
| EndProject | EndProject | ||||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}" | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}" | ||||||
| EndProject | EndProject | ||||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{272E6092-BD31-4EB6-A9FF-F4179F91958F}" |  | ||||||
| EndProject |  | ||||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20595AD4-8D75-4AF8-B6BC-9C38C160423F}" | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20595AD4-8D75-4AF8-B6BC-9C38C160423F}" | ||||||
| 	ProjectSection(SolutionItems) = preProject | 	ProjectSection(SolutionItems) = preProject | ||||||
| 		.editorconfig = .editorconfig | 		.editorconfig = .editorconfig | ||||||
| @@ -17,6 +15,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | |||||||
| 		stylecop.json = stylecop.json | 		stylecop.json = stylecop.json | ||||||
| 	EndProjectSection | 	EndProjectSection | ||||||
| EndProject | EndProject | ||||||
|  | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F0575243-121F-4DEE-9F6B-246E26DC0844}" | ||||||
|  | EndProject | ||||||
|  | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Table", "..\examples\Table\Table.csproj", "{94ECCBA8-7EBF-4B53-8379-52EB2327417E}" | ||||||
|  | EndProject | ||||||
|  | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Panel", "..\examples\Panel\Panel.csproj", "{BFF37228-B376-4ADD-9657-4E501F929713}" | ||||||
|  | EndProject | ||||||
|  | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grid", "..\examples\Grid\Grid.csproj", "{C7FF6FDB-FB59-4517-8669-521C96AB7323}" | ||||||
|  | EndProject | ||||||
|  | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Colors", "..\examples\Colors\Colors.csproj", "{1F51C55C-BA4C-4856-9001-0F7924FFB179}" | ||||||
|  | EndProject | ||||||
| Global | Global | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
| 		Debug|Any CPU = Debug|Any CPU | 		Debug|Any CPU = Debug|Any CPU | ||||||
| @@ -51,22 +59,64 @@ Global | |||||||
| 		{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x64.Build.0 = Release|Any CPU | 		{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x64.Build.0 = Release|Any CPU | ||||||
| 		{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x86.ActiveCfg = Release|Any CPU | 		{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
| 		{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x86.Build.0 = Release|Any CPU | 		{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}.Release|x86.Build.0 = Release|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|Any CPU.Build.0 = Debug|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x64.ActiveCfg = Debug|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x64.Build.0 = Debug|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x86.ActiveCfg = Debug|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x86.Build.0 = Debug|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|Any CPU.ActiveCfg = Release|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|Any CPU.Build.0 = Release|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x64.ActiveCfg = Release|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x64.Build.0 = Release|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x64.Build.0 = Release|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x86.ActiveCfg = Release|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x86.Build.0 = Release|Any CPU | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323}.Release|x86.Build.0 = Release|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x64.Build.0 = Debug|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Debug|x86.Build.0 = Debug|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|Any CPU.Build.0 = Release|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x64.ActiveCfg = Release|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x64.Build.0 = Release|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x86.ActiveCfg = Release|Any CPU | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179}.Release|x86.Build.0 = Release|Any CPU | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(SolutionProperties) = preSolution | 	GlobalSection(SolutionProperties) = preSolution | ||||||
| 		HideSolutionNode = FALSE | 		HideSolutionNode = FALSE | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
|  | 	GlobalSection(NestedProjects) = preSolution | ||||||
|  | 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||||
|  | 		{BFF37228-B376-4ADD-9657-4E501F929713} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||||
|  | 		{C7FF6FDB-FB59-4517-8669-521C96AB7323} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||||
|  | 		{1F51C55C-BA4C-4856-9001-0F7924FFB179} = {F0575243-121F-4DEE-9F6B-246E26DC0844} | ||||||
|  | 	EndGlobalSection | ||||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||||
| 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| using Spectre.Console.Composition; | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
| namespace Spectre.Console | namespace Spectre.Console | ||||||
| { | { | ||||||
|   | |||||||
| @@ -21,6 +21,6 @@ namespace Spectre.Console | |||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Gets or sets the out buffer. |         /// Gets or sets the out buffer. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public TextWriter Out { get; set; } |         public TextWriter? Out { get; set; } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,13 @@ namespace Spectre.Console | |||||||
|         /// </remarks> |         /// </remarks> | ||||||
|         public bool LegacyConsole { get; } |         public bool LegacyConsole { get; } | ||||||
|  |  | ||||||
|         internal Capabilities(bool supportsAnsi, ColorSystem colorSystem, bool legacyConsole) |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Capabilities"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="supportsAnsi">Whether or not ANSI escape sequences are supported.</param> | ||||||
|  |         /// <param name="colorSystem">The color system that is supported.</param> | ||||||
|  |         /// <param name="legacyConsole">Whether or not this is a legacy console.</param> | ||||||
|  |         public Capabilities(bool supportsAnsi, ColorSystem colorSystem, bool legacyConsole) | ||||||
|         { |         { | ||||||
|             SupportsAnsi = supportsAnsi; |             SupportsAnsi = supportsAnsi; | ||||||
|             ColorSystem = colorSystem; |             ColorSystem = colorSystem; | ||||||
| @@ -44,7 +50,7 @@ namespace Spectre.Console | |||||||
|                 ColorSystem.Standard => "4 bits", |                 ColorSystem.Standard => "4 bits", | ||||||
|                 ColorSystem.EightBit => "8 bits", |                 ColorSystem.EightBit => "8 bits", | ||||||
|                 ColorSystem.TrueColor => "24 bits", |                 ColorSystem.TrueColor => "24 bits", | ||||||
|                 _ => "?" |                 _ => "?", | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})"; |             return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})"; | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ namespace Spectre.Console | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |         /// <inheritdoc/> | ||||||
|         public override bool Equals(object obj) |         public override bool Equals(object? obj) | ||||||
|         { |         { | ||||||
|             return obj is Color color && Equals(color); |             return obj is Color color && Equals(color); | ||||||
|         } |         } | ||||||
| @@ -82,7 +82,8 @@ namespace Spectre.Console | |||||||
|         /// <inheritdoc/> |         /// <inheritdoc/> | ||||||
|         public bool Equals(Color other) |         public bool Equals(Color other) | ||||||
|         { |         { | ||||||
|             return R == other.R && G == other.G && B == other.B; |             return (IsDefault && other.IsDefault) || | ||||||
|  |                    (IsDefault == other.IsDefault && R == other.R && G == other.G && B == other.B); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|   | |||||||
| @@ -1,10 +0,0 @@ | |||||||
| namespace Spectre.Console.Composition |  | ||||||
| { |  | ||||||
|     internal sealed class NoBorder : Border |  | ||||||
|     { |  | ||||||
|         protected override string GetBoxPart(BorderPart part) |  | ||||||
|         { |  | ||||||
|             return " "; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,79 +0,0 @@ | |||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Text; |  | ||||||
| using Spectre.Console.Composition; |  | ||||||
|  |  | ||||||
| namespace Spectre.Console |  | ||||||
| { |  | ||||||
|     /// <summary> |  | ||||||
|     /// Represents a grid. |  | ||||||
|     /// </summary> |  | ||||||
|     public sealed class Grid : IRenderable |  | ||||||
|     { |  | ||||||
|         private readonly Table _table; |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Initializes a new instance of the <see cref="Grid"/> class. |  | ||||||
|         /// </summary> |  | ||||||
|         public Grid() |  | ||||||
|         { |  | ||||||
|             _table = new Table(BorderKind.None, showHeaders: false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |  | ||||||
|         public int Measure(Encoding encoding, int maxWidth) |  | ||||||
|         { |  | ||||||
|             return _table.Measure(encoding, maxWidth); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |  | ||||||
|         public IEnumerable<Segment> Render(Encoding encoding, int width) |  | ||||||
|         { |  | ||||||
|             return _table.Render(encoding, width); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Adds a single column to the grid. |  | ||||||
|         /// </summary> |  | ||||||
|         public void AddColumn() |  | ||||||
|         { |  | ||||||
|             _table.AddColumn(string.Empty); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Adds the specified number of columns to the grid. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="count">The number of columns.</param> |  | ||||||
|         public void AddColumns(int count) |  | ||||||
|         { |  | ||||||
|             for (var i = 0; i < count; i++) |  | ||||||
|             { |  | ||||||
|                 _table.AddColumn(string.Empty); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Adds a new row to the grid. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="columns">The columns to add.</param> |  | ||||||
|         public void AddRow(params string[] columns) |  | ||||||
|         { |  | ||||||
|             if (columns is null) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentNullException(nameof(columns)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (columns.Length < _table.ColumnCount) |  | ||||||
|             { |  | ||||||
|                 throw new InvalidOperationException("The number of row columns are less than the number of grid columns."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (columns.Length > _table.ColumnCount) |  | ||||||
|             { |  | ||||||
|                 throw new InvalidOperationException("The number of row columns are greater than the number of grid columns."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             _table.AddRow(columns); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,119 +0,0 @@ | |||||||
| 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; |  | ||||||
|         private readonly Justify _content; |  | ||||||
|  |  | ||||||
|         /// <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> |  | ||||||
|         /// <param name="content">The justification of the panel content.</param> |  | ||||||
|         public Panel(IRenderable child, bool fit = false, Justify content = Justify.Left) |  | ||||||
|         { |  | ||||||
|             _child = child; |  | ||||||
|             _fit = fit; |  | ||||||
|             _content = content; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <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")); |  | ||||||
|  |  | ||||||
|             // Render the child. |  | ||||||
|             var childSegments = _child.Render(encoding, childWidth); |  | ||||||
|  |  | ||||||
|             // Split the child segments into lines. |  | ||||||
|             var lines = Segment.SplitLines(childSegments, childWidth); |  | ||||||
|             foreach (var line in lines) |  | ||||||
|             { |  | ||||||
|                 result.Add(new Segment("│ ")); |  | ||||||
|  |  | ||||||
|                 var content = new List<Segment>(); |  | ||||||
|  |  | ||||||
|                 var length = line.Sum(segment => segment.CellLength(encoding)); |  | ||||||
|                 if (length < childWidth) |  | ||||||
|                 { |  | ||||||
|                     if (_content == Justify.Right) |  | ||||||
|                     { |  | ||||||
|                         var diff = childWidth - length; |  | ||||||
|                         content.Add(new Segment(new string(' ', diff))); |  | ||||||
|                     } |  | ||||||
|                     else if (_content == Justify.Center) |  | ||||||
|                     { |  | ||||||
|                         var diff = (childWidth - length) / 2; |  | ||||||
|                         content.Add(new Segment(new string(' ', diff))); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 foreach (var segment in line) |  | ||||||
|                 { |  | ||||||
|                     content.Add(segment.StripLineEndings()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (length < childWidth) |  | ||||||
|                 { |  | ||||||
|                     if (_content == Justify.Left) |  | ||||||
|                     { |  | ||||||
|                         var diff = childWidth - length; |  | ||||||
|                         content.Add(new Segment(new string(' ', diff))); |  | ||||||
|                     } |  | ||||||
|                     else if (_content == Justify.Center) |  | ||||||
|                     { |  | ||||||
|                         var diff = (childWidth - length) / 2; |  | ||||||
|                         content.Add(new Segment(new string(' ', diff))); |  | ||||||
|  |  | ||||||
|                         var remainder = (childWidth - length) % 2; |  | ||||||
|                         if (remainder != 0) |  | ||||||
|                         { |  | ||||||
|                             content.Add(new Segment(new string(' ', remainder))); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 result.AddRange(content); |  | ||||||
|  |  | ||||||
|                 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; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,18 +0,0 @@ | |||||||
| using System.Collections.Generic; |  | ||||||
| using System.Diagnostics.CodeAnalysis; |  | ||||||
| using System.Linq; |  | ||||||
|  |  | ||||||
| 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> |  | ||||||
|     { |  | ||||||
|         /// <summary> |  | ||||||
|         /// Gets the length of the line. |  | ||||||
|         /// </summary> |  | ||||||
|         public int Length => this.Sum(line => line.Text.Length); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,297 +0,0 @@ | |||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Linq; |  | ||||||
| using System.Text; |  | ||||||
| using Spectre.Console.Composition; |  | ||||||
| using Spectre.Console.Internal; |  | ||||||
|  |  | ||||||
| namespace Spectre.Console |  | ||||||
| { |  | ||||||
|     /// <summary> |  | ||||||
|     /// Represents a table. |  | ||||||
|     /// </summary> |  | ||||||
|     public sealed class Table : IRenderable |  | ||||||
|     { |  | ||||||
|         private readonly List<Text> _columns; |  | ||||||
|         private readonly List<List<Text>> _rows; |  | ||||||
|         private readonly Border _border; |  | ||||||
|         private readonly BorderKind _borderKind; |  | ||||||
|         private readonly bool _showHeaders; |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Gets the number of columns in the table. |  | ||||||
|         /// </summary> |  | ||||||
|         public int ColumnCount => _columns.Count; |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Initializes a new instance of the <see cref="Table"/> class. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="border">The border to use.</param> |  | ||||||
|         /// <param name="showHeaders">Whether or not to show table headers.</param> |  | ||||||
|         public Table(BorderKind border = BorderKind.Square, bool showHeaders = true) |  | ||||||
|         { |  | ||||||
|             _columns = new List<Text>(); |  | ||||||
|             _rows = new List<List<Text>>(); |  | ||||||
|             _border = Border.GetBorder(border); |  | ||||||
|             _borderKind = border; |  | ||||||
|             _showHeaders = showHeaders; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Adds a column to the table. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="column">The column to add.</param> |  | ||||||
|         public void AddColumn(string column) |  | ||||||
|         { |  | ||||||
|             if (column is null) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentNullException(nameof(column)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             _columns.Add(Text.New(column)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Adds multiple columns to the table. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="columns">The columns to add.</param> |  | ||||||
|         public void AddColumns(params string[] columns) |  | ||||||
|         { |  | ||||||
|             if (columns is null) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentNullException(nameof(columns)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             _columns.AddRange(columns.Select(column => Text.New(column))); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Adds a row to the table. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="columns">The row columns to add.</param> |  | ||||||
|         public void AddRow(params string[] columns) |  | ||||||
|         { |  | ||||||
|             if (columns is null) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentNullException(nameof(columns)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (columns.Length < _columns.Count) |  | ||||||
|             { |  | ||||||
|                 throw new InvalidOperationException("The number of row columns are less than the number of table columns."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (columns.Length > _columns.Count) |  | ||||||
|             { |  | ||||||
|                 throw new InvalidOperationException("The number of row columns are greater than the number of table columns."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             _rows.Add(columns.Select(column => Text.New(column)).ToList()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |  | ||||||
|         public int Measure(Encoding encoding, int maxWidth) |  | ||||||
|         { |  | ||||||
|             // Calculate the max width for each column |  | ||||||
|             var maxColumnWidth = (maxWidth - (2 + (_columns.Count * 2) + (_columns.Count - 1))) / _columns.Count; |  | ||||||
|             var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray(); |  | ||||||
|             for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++) |  | ||||||
|             { |  | ||||||
|                 for (var columnIndex = 0; columnIndex < _rows[rowIndex].Count; columnIndex++) |  | ||||||
|                 { |  | ||||||
|                     var columnWidth = _rows[rowIndex][columnIndex].Measure(encoding, maxColumnWidth); |  | ||||||
|                     if (columnWidth > columnWidths[columnIndex]) |  | ||||||
|                     { |  | ||||||
|                         columnWidths[columnIndex] = columnWidth; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // We now know the max width of each column, so let's recalculate the width |  | ||||||
|             return columnWidths.Sum() + 2 + (_columns.Count * 2) + (_columns.Count - 1); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |  | ||||||
|         public IEnumerable<Segment> Render(Encoding encoding, int width) |  | ||||||
|         { |  | ||||||
|             var showBorder = _borderKind != BorderKind.None; |  | ||||||
|             var hideBorder = _borderKind == BorderKind.None; |  | ||||||
|  |  | ||||||
|             var leftRightBorderWidth = _borderKind == BorderKind.None ? 0 : 2; |  | ||||||
|             var columnPadding = _borderKind == BorderKind.None ? _columns.Count : _columns.Count * 2; |  | ||||||
|             var separatorCount = _borderKind == BorderKind.None ? 0 : _columns.Count - 1; |  | ||||||
|  |  | ||||||
|             // Calculate the max width for each column. |  | ||||||
|             var maxColumnWidth = (width - (leftRightBorderWidth + columnPadding + separatorCount)) / _columns.Count; |  | ||||||
|             var columnWidths = _columns.Select(c => c.Measure(encoding, maxColumnWidth)).ToArray(); |  | ||||||
|             for (var rowIndex = 0; rowIndex < _rows.Count; rowIndex++) |  | ||||||
|             { |  | ||||||
|                 for (var columnIndex = 0; columnIndex < _rows[rowIndex].Count; columnIndex++) |  | ||||||
|                 { |  | ||||||
|                     var columnWidth = _rows[rowIndex][columnIndex].Measure(encoding, maxColumnWidth); |  | ||||||
|                     if (columnWidth > columnWidths[columnIndex]) |  | ||||||
|                     { |  | ||||||
|                         columnWidths[columnIndex] = columnWidth; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // We now know the max width of each column, so let's recalculate the width |  | ||||||
|             width = columnWidths.Sum() + leftRightBorderWidth + columnPadding + separatorCount; |  | ||||||
|  |  | ||||||
|             var rows = new List<List<Text>>(); |  | ||||||
|             if (_showHeaders) |  | ||||||
|             { |  | ||||||
|                 // Add columns to top of rows |  | ||||||
|                 rows.Add(new List<Text>(_columns)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Add tows. |  | ||||||
|             rows.AddRange(_rows); |  | ||||||
|  |  | ||||||
|             // Iterate all rows. |  | ||||||
|             var result = new List<Segment>(); |  | ||||||
|             foreach (var (index, firstRow, lastRow, row) in rows.Enumerate()) |  | ||||||
|             { |  | ||||||
|                 var cellHeight = 1; |  | ||||||
|  |  | ||||||
|                 // Get the list of cells for the row and calculate the cell height |  | ||||||
|                 var cells = new List<List<SegmentLine>>(); |  | ||||||
|                 foreach (var (rowWidth, cell) in columnWidths.Zip(row, (f, s) => (f, s))) |  | ||||||
|                 { |  | ||||||
|                     var lines = Segment.SplitLines(cell.Render(encoding, rowWidth)); |  | ||||||
|                     cellHeight = Math.Max(cellHeight, lines.Count); |  | ||||||
|                     cells.Add(lines); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Show top of header? |  | ||||||
|                 if (firstRow && showBorder) |  | ||||||
|                 { |  | ||||||
|                     result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopLeft))); |  | ||||||
|                     foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate()) |  | ||||||
|                     { |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop))); // Left padding |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop, columnWidth))); |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.HeaderTop))); // Right padding |  | ||||||
|  |  | ||||||
|                         if (!lastColumn) |  | ||||||
|                         { |  | ||||||
|                             result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopSeparator))); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     result.Add(new Segment(_border.GetPart(BorderPart.HeaderTopRight))); |  | ||||||
|                     result.Add(Segment.LineBreak()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Iterate through each cell row |  | ||||||
|                 foreach (var cellRowIndex in Enumerable.Range(0, cellHeight)) |  | ||||||
|                 { |  | ||||||
|                     // Make cells the same shape |  | ||||||
|                     MakeSameHeight(cellHeight, cells); |  | ||||||
|  |  | ||||||
|                     var w00t = cells.Enumerate().ToArray(); |  | ||||||
|                     foreach (var (cellIndex, firstCell, lastCell, cell) in w00t) |  | ||||||
|                     { |  | ||||||
|                         if (firstCell && showBorder) |  | ||||||
|                         { |  | ||||||
|                             // Show left column edge |  | ||||||
|                             result.Add(new Segment(_border.GetPart(BorderPart.CellLeft))); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         // Pad column on left side. |  | ||||||
|                         if (showBorder) |  | ||||||
|                         { |  | ||||||
|                             result.Add(new Segment(" ")); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         // Add content |  | ||||||
|                         result.AddRange(cell[cellRowIndex]); |  | ||||||
|  |  | ||||||
|                         // Pad cell content right |  | ||||||
|                         var length = cell[cellRowIndex].Sum(segment => segment.CellLength(encoding)); |  | ||||||
|                         if (length < columnWidths[cellIndex]) |  | ||||||
|                         { |  | ||||||
|                             result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length))); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         // Pad column on the right side |  | ||||||
|                         if (showBorder || (hideBorder && !lastCell)) |  | ||||||
|                         { |  | ||||||
|                             result.Add(new Segment(" ")); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         if (lastCell && showBorder) |  | ||||||
|                         { |  | ||||||
|                             // Add right column edge |  | ||||||
|                             result.Add(new Segment(_border.GetPart(BorderPart.ColumnRight))); |  | ||||||
|                         } |  | ||||||
|                         else if (showBorder || (hideBorder && !lastCell)) |  | ||||||
|                         { |  | ||||||
|                             // Add column separator |  | ||||||
|                             result.Add(new Segment(_border.GetPart(BorderPart.CellSeparator))); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     result.Add(Segment.LineBreak()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Show bottom of header? |  | ||||||
|                 if (firstRow && showBorder) |  | ||||||
|                 { |  | ||||||
|                     result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomLeft))); |  | ||||||
|                     foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate()) |  | ||||||
|                     { |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom))); // Left padding |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom, columnWidth))); |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottom))); // Right padding |  | ||||||
|  |  | ||||||
|                         if (!lastColumn) |  | ||||||
|                         { |  | ||||||
|                             result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomSeparator))); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     result.Add(new Segment(_border.GetPart(BorderPart.HeaderBottomRight))); |  | ||||||
|                     result.Add(Segment.LineBreak()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Show bottom of footer? |  | ||||||
|                 if (lastRow && showBorder) |  | ||||||
|                 { |  | ||||||
|                     result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomLeft))); |  | ||||||
|                     foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate()) |  | ||||||
|                     { |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom))); |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom, columnWidth))); |  | ||||||
|                         result.Add(new Segment(_border.GetPart(BorderPart.FooterBottom))); |  | ||||||
|  |  | ||||||
|                         if (!lastColumn) |  | ||||||
|                         { |  | ||||||
|                             result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomSeparator))); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     result.Add(new Segment(_border.GetPart(BorderPart.FooterBottomRight))); |  | ||||||
|                     result.Add(Segment.LineBreak()); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private static void MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells) |  | ||||||
|         { |  | ||||||
|             foreach (var cell in cells) |  | ||||||
|             { |  | ||||||
|                 if (cell.Count < cellHeight) |  | ||||||
|                 { |  | ||||||
|                     while (cell.Count != cellHeight) |  | ||||||
|                     { |  | ||||||
|                         cell.Add(new SegmentLine()); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,225 +0,0 @@ | |||||||
| using System; |  | ||||||
| using System.Collections.Generic; |  | ||||||
| using System.Diagnostics; |  | ||||||
| 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 decorations. |  | ||||||
|     /// </summary> |  | ||||||
|     [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] |  | ||||||
|     [DebuggerDisplay("{_text,nq}")] |  | ||||||
|     public sealed class Text : IRenderable |  | ||||||
|     { |  | ||||||
|         private readonly List<Span> _spans; |  | ||||||
|         private string _text; |  | ||||||
|  |  | ||||||
|         private sealed class Span |  | ||||||
|         { |  | ||||||
|             public int Start { get; } |  | ||||||
|             public int End { get; } |  | ||||||
|             public Style Style { get; } |  | ||||||
|  |  | ||||||
|             public Span(int start, int end, Style style) |  | ||||||
|             { |  | ||||||
|                 Start = start; |  | ||||||
|                 End = end; |  | ||||||
|                 Style = style ?? Style.Plain; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Initializes a new instance of the <see cref="Console.Text"/> class. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="text">The text.</param> |  | ||||||
|         internal Text(string text) |  | ||||||
|         { |  | ||||||
|             _text = text ?? throw new ArgumentNullException(nameof(text)); |  | ||||||
|             _spans = new List<Span>(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <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="decoration">The text decoration.</param> |  | ||||||
|         /// <returns>A <see cref="Text"/> instance.</returns> |  | ||||||
|         public static Text New( |  | ||||||
|             string text, Color? foreground = null, Color? background = null, Decoration? decoration = null) |  | ||||||
|         { |  | ||||||
|             var result = MarkupParser.Parse(text, new Style(foreground, background, decoration)); |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Appends some text with the specified color and decorations. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="text">The text to append.</param> |  | ||||||
|         /// <param name="style">The text style.</param> |  | ||||||
|         public void Append(string text, Style style) |  | ||||||
|         { |  | ||||||
|             if (text == null) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentNullException(nameof(text)); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             var start = _text.Length; |  | ||||||
|             var end = _text.Length + text.Length; |  | ||||||
|  |  | ||||||
|             _text += text; |  | ||||||
|  |  | ||||||
|             Stylize(start, end, style); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <summary> |  | ||||||
|         /// Stylizes a part of the text. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <param name="start">The start position.</param> |  | ||||||
|         /// <param name="end">The end position.</param> |  | ||||||
|         /// <param name="style">The style to apply.</param> |  | ||||||
|         public void Stylize(int start, int end, Style style) |  | ||||||
|         { |  | ||||||
|             if (start >= end) |  | ||||||
|             { |  | ||||||
|                 throw new ArgumentOutOfRangeException(nameof(start), "Start position must be less than the end position."); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             start = Math.Max(start, 0); |  | ||||||
|             end = Math.Min(end, _text.Length); |  | ||||||
|  |  | ||||||
|             _spans.Add(new Span(start, end, style)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |  | ||||||
|         public int Measure(Encoding encoding, int maxWidth) |  | ||||||
|         { |  | ||||||
|             var lines = Segment.SplitLines(Render(encoding, maxWidth)); |  | ||||||
|             if (lines.Count == 0) |  | ||||||
|             { |  | ||||||
|                 return 0; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return lines.Max(x => x.Length); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |  | ||||||
|         public IEnumerable<Segment> Render(Encoding encoding, int width) |  | ||||||
|         { |  | ||||||
|             if (string.IsNullOrWhiteSpace(_text)) |  | ||||||
|             { |  | ||||||
|                 return Array.Empty<Segment>(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             var result = new List<Segment>(); |  | ||||||
|             var segments = SplitLineBreaks(CreateSegments()); |  | ||||||
|  |  | ||||||
|             foreach (var (_, _, last, line) in Segment.SplitLines(segments, width).Enumerate()) |  | ||||||
|             { |  | ||||||
|                 foreach (var segment in line) |  | ||||||
|                 { |  | ||||||
|                     result.Add(segment.StripLineEndings()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (!last) |  | ||||||
|                 { |  | ||||||
|                     result.Add(Segment.LineBreak()); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private IEnumerable<Segment> SplitLineBreaks(IEnumerable<Segment> segments) |  | ||||||
|         { |  | ||||||
|             // Creates individual segments of line breaks. |  | ||||||
|             var result = new List<Segment>(); |  | ||||||
|             var queue = new Stack<Segment>(segments.Reverse()); |  | ||||||
|  |  | ||||||
|             while (queue.Count > 0) |  | ||||||
|             { |  | ||||||
|                 var segment = queue.Pop(); |  | ||||||
|  |  | ||||||
|                 var index = segment.Text.IndexOf("\n", StringComparison.OrdinalIgnoreCase); |  | ||||||
|                 if (index == -1) |  | ||||||
|                 { |  | ||||||
|                     result.Add(segment); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     var (first, second) = segment.Split(index); |  | ||||||
|                     if (!string.IsNullOrEmpty(first.Text)) |  | ||||||
|                     { |  | ||||||
|                         result.Add(first); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     result.Add(Segment.LineBreak()); |  | ||||||
|                     queue.Push(new Segment(second.Text.Substring(1), second.Style)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private IEnumerable<Segment> CreateSegments() |  | ||||||
|         { |  | ||||||
|             // This excellent algorithm to sort spans was ported and adapted from |  | ||||||
|             // https://github.com/willmcgugan/rich/blob/eb2f0d5277c159d8693636ec60c79c5442fd2e43/rich/text.py#L492 |  | ||||||
|  |  | ||||||
|             // Create the style map. |  | ||||||
|             var styleMap = _spans.SelectIndex((span, index) => (span, index)).ToDictionary(x => x.index + 1, x => x.span.Style); |  | ||||||
|             styleMap[0] = Style.Plain; |  | ||||||
|  |  | ||||||
|             // Create a span list. |  | ||||||
|             var spans = new List<(int Offset, bool Leaving, int Style)>(); |  | ||||||
|             spans.AddRange(_spans.SelectIndex((span, index) => (span.Start, false, index + 1))); |  | ||||||
|             spans.AddRange(_spans.SelectIndex((span, index) => (span.End, true, index + 1))); |  | ||||||
|             spans = spans.OrderBy(x => x.Offset).ThenBy(x => !x.Leaving).ToList(); |  | ||||||
|  |  | ||||||
|             // Keep track of applied styles using a stack |  | ||||||
|             var styleStack = new Stack<int>(); |  | ||||||
|  |  | ||||||
|             // Now build the segments. |  | ||||||
|             var result = new List<Segment>(); |  | ||||||
|             foreach (var (offset, leaving, style, nextOffset) in BuildSkipList(spans)) |  | ||||||
|             { |  | ||||||
|                 if (leaving) |  | ||||||
|                 { |  | ||||||
|                     // Leaving |  | ||||||
|                     styleStack.Pop(); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     // Entering |  | ||||||
|                     styleStack.Push(style); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (nextOffset > offset) |  | ||||||
|                 { |  | ||||||
|                     // Build the current style from the stack |  | ||||||
|                     var styleIndices = styleStack.OrderBy(index => index).ToArray(); |  | ||||||
|                     var currentStyle = Style.Plain.Combine(styleIndices.Select(index => styleMap[index])); |  | ||||||
|  |  | ||||||
|                     // Create segment |  | ||||||
|                     var text = _text.Substring(offset, Math.Min(_text.Length - offset, nextOffset - offset)); |  | ||||||
|                     result.Add(new Segment(text, currentStyle)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private static IEnumerable<(int Offset, bool Leaving, int Style, int NextOffset)> BuildSkipList( |  | ||||||
|             List<(int Offset, bool Leaving, int Style)> spans) |  | ||||||
|         { |  | ||||||
|             return spans.Zip(spans.Skip(1), (first, second) => (first, second)).Select( |  | ||||||
|                     x => (x.first.Offset, x.first.Leaving, x.first.Style, NextOffset: x.second.Offset)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| using System; | using System; | ||||||
| using Spectre.Console.Composition; |  | ||||||
| using Spectre.Console.Internal; | using Spectre.Console.Internal; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
| namespace Spectre.Console | namespace Spectre.Console | ||||||
| { | { | ||||||
| @@ -26,17 +26,26 @@ namespace Spectre.Console | |||||||
|                 throw new ArgumentNullException(nameof(renderable)); |                 throw new ArgumentNullException(nameof(renderable)); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             foreach (var segment in renderable.Render(console.Encoding, console.Width)) |             var options = new RenderContext(console.Encoding, console.Capabilities.LegacyConsole); | ||||||
|  |  | ||||||
|  |             using (console.PushStyle(Style.Plain)) | ||||||
|             { |             { | ||||||
|                 if (!segment.Style.Equals(Style.Plain)) |                 var current = Style.Plain; | ||||||
|  |                 foreach (var segment in renderable.Render(options, console.Width)) | ||||||
|                 { |                 { | ||||||
|                     using (var style = console.PushStyle(segment.Style)) |                     if (string.IsNullOrEmpty(segment.Text)) | ||||||
|                     { |                     { | ||||||
|                         console.Write(segment.Text); |                         continue; | ||||||
|                     } |                     } | ||||||
|                 } |  | ||||||
|                 else |                     if (!segment.Style.Equals(current)) | ||||||
|                 { |                     { | ||||||
|  |                         console.Foreground = segment.Style.Foreground; | ||||||
|  |                         console.Background = segment.Style.Background; | ||||||
|  |                         console.Decoration = segment.Style.Decoration; | ||||||
|  |                         current = segment.Style; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     console.Write(segment.Text); |                     console.Write(segment.Text); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -60,12 +60,19 @@ namespace Spectre.Console.Internal | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             _out.Write(AnsiBuilder.GetAnsi( |             var parts = text.NormalizeLineEndings().Split(new[] { '\n' }); | ||||||
|                 _system, |             foreach (var (_, _, last, part) in parts.Enumerate()) | ||||||
|                 text.NormalizeLineEndings(native: true), |             { | ||||||
|                 Decoration, |                 if (!string.IsNullOrEmpty(part)) | ||||||
|                 Foreground, |                 { | ||||||
|                 Background)); |                     _out.Write(AnsiBuilder.GetAnsi(_system, part, Decoration, Foreground, Background)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (!last) | ||||||
|  |                 { | ||||||
|  |                     _out.Write(Environment.NewLine); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -19,7 +19,7 @@ namespace Spectre.Console.Internal | |||||||
|             { |             { | ||||||
|                 if (supportsAnsi) |                 if (supportsAnsi) | ||||||
|                 { |                 { | ||||||
|                     var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)$"); |                     var regex = new Regex("^Microsoft Windows (?'major'[0-9]*).(?'minor'[0-9]*).(?'build'[0-9]*)\\s*$"); | ||||||
|                     var match = regex.Match(RuntimeInformation.OSDescription); |                     var match = regex.Match(RuntimeInformation.OSDescription); | ||||||
|                     if (match.Success && int.TryParse(match.Groups["major"].Value, out var major)) |                     if (match.Success && int.TryParse(match.Groups["major"].Value, out var major)) | ||||||
|                     { |                     { | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ namespace Spectre.Console.Internal | |||||||
|             return ColorPalette.EightBit[number]; |             return ColorPalette.EightBit[number]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static string GetName(int number) |         public static string? GetName(int number) | ||||||
|         { |         { | ||||||
|             _nameLookup.TryGetValue(number, out var name); |             _nameLookup.TryGetValue(number, out var name); | ||||||
|             return name; |             return name; | ||||||
|   | |||||||
| @@ -15,14 +15,18 @@ namespace Spectre.Console.Internal | |||||||
|             { |             { | ||||||
|                 { "none", Decoration.None }, |                 { "none", Decoration.None }, | ||||||
|                 { "bold", Decoration.Bold }, |                 { "bold", Decoration.Bold }, | ||||||
|  |                 { "b", Decoration.Bold }, | ||||||
|                 { "dim", Decoration.Dim }, |                 { "dim", Decoration.Dim }, | ||||||
|                 { "italic", Decoration.Italic }, |                 { "italic", Decoration.Italic }, | ||||||
|  |                 { "i", Decoration.Italic }, | ||||||
|                 { "underline", Decoration.Underline }, |                 { "underline", Decoration.Underline }, | ||||||
|  |                 { "u", Decoration.Underline }, | ||||||
|                 { "invert", Decoration.Invert }, |                 { "invert", Decoration.Invert }, | ||||||
|                 { "conceal", Decoration.Conceal }, |                 { "conceal", Decoration.Conceal }, | ||||||
|                 { "slowblink", Decoration.SlowBlink }, |                 { "slowblink", Decoration.SlowBlink }, | ||||||
|                 { "rapidblink", Decoration.RapidBlink }, |                 { "rapidblink", Decoration.RapidBlink }, | ||||||
|                 { "strikethrough", Decoration.Strikethrough }, |                 { "strikethrough", Decoration.Strikethrough }, | ||||||
|  |                 { "s", Decoration.Strikethrough }, | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,19 @@ namespace Spectre.Console.Internal | |||||||
| { | { | ||||||
|     internal static class EnumerableExtensions |     internal static class EnumerableExtensions | ||||||
|     { |     { | ||||||
|  |         public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) | ||||||
|  |         { | ||||||
|  |             foreach (var item in source) | ||||||
|  |             { | ||||||
|  |                 action(item); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static bool AnyTrue(this IEnumerable<bool> source) | ||||||
|  |         { | ||||||
|  |             return source.Any(b => b); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source) |         public static IEnumerable<(int Index, bool First, bool Last, T Item)> Enumerate<T>(this IEnumerable<T> source) | ||||||
|         { |         { | ||||||
|             if (source is null) |             if (source is null) | ||||||
| @@ -40,5 +53,18 @@ namespace Spectre.Console.Internal | |||||||
|         { |         { | ||||||
|             return source.Select((value, index) => func(value, index)); |             return source.Select((value, index) => func(value, index)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>( | ||||||
|  |             this IEnumerable<TFirst> source, IEnumerable<TSecond> first) | ||||||
|  |         { | ||||||
|  |             return source.Zip(first, (first, second) => (first, second)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>( | ||||||
|  |             this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third) | ||||||
|  |         { | ||||||
|  |             return first.Zip(second, (a, b) => (a, b)) | ||||||
|  |                 .Zip(third, (a, b) => (a.a, a.b, b)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
| using System.Text; | using System.Text; | ||||||
|  |  | ||||||
| namespace Spectre.Console.Internal | namespace Spectre.Console.Internal | ||||||
| @@ -17,9 +18,9 @@ namespace Spectre.Console.Internal | |||||||
|  |  | ||||||
|         public static string NormalizeLineEndings(this string text, bool native = false) |         public static string NormalizeLineEndings(this string text, bool native = false) | ||||||
|         { |         { | ||||||
|             var normalized = text?.Replace("\r\n", "\n") |             text ??= string.Empty; | ||||||
|                 ?.Replace("\r", string.Empty); |  | ||||||
|  |  | ||||||
|  |             var normalized = text?.Replace("\r\n", "\n")?.Replace("\r", string.Empty) ?? string.Empty; | ||||||
|             if (native && !_alreadyNormalized) |             if (native && !_alreadyNormalized) | ||||||
|             { |             { | ||||||
|                 normalized = normalized.Replace("\n", Environment.NewLine); |                 normalized = normalized.Replace("\n", Environment.NewLine); | ||||||
| @@ -30,7 +31,52 @@ namespace Spectre.Console.Internal | |||||||
|  |  | ||||||
|         public static string[] SplitLines(this string text) |         public static string[] SplitLines(this string text) | ||||||
|         { |         { | ||||||
|             return text.NormalizeLineEndings().Split(new[] { '\n' }, StringSplitOptions.None); |             var result = text?.NormalizeLineEndings()?.Split(new[] { '\n' }, StringSplitOptions.None); | ||||||
|  |             return result ?? Array.Empty<string>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static string[] SplitWords(this string word, StringSplitOptions options = StringSplitOptions.None) | ||||||
|  |         { | ||||||
|  |             var result = new List<string>(); | ||||||
|  |  | ||||||
|  |             static string Read(StringBuffer reader, Func<char, bool> criteria) | ||||||
|  |             { | ||||||
|  |                 var buffer = new StringBuilder(); | ||||||
|  |                 while (!reader.Eof) | ||||||
|  |                 { | ||||||
|  |                     var current = reader.Peek(); | ||||||
|  |                     if (!criteria(current)) | ||||||
|  |                     { | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     buffer.Append(reader.Read()); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return buffer.ToString(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             using (var reader = new StringBuffer(word)) | ||||||
|  |             { | ||||||
|  |                 while (!reader.Eof) | ||||||
|  |                 { | ||||||
|  |                     var current = reader.Peek(); | ||||||
|  |                     if (char.IsWhiteSpace(current)) | ||||||
|  |                     { | ||||||
|  |                         var x = Read(reader, c => char.IsWhiteSpace(c)); | ||||||
|  |                         if (options != StringSplitOptions.RemoveEmptyEntries) | ||||||
|  |                         { | ||||||
|  |                             result.Add(x); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         result.Add(Read(reader, c => !char.IsWhiteSpace(c))); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result.ToArray(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -70,6 +70,11 @@ namespace Spectre.Console.Internal | |||||||
|  |  | ||||||
|         public static int GetCellLength(Encoding encoding, char rune) |         public static int GetCellLength(Encoding encoding, char rune) | ||||||
|         { |         { | ||||||
|  |             if (rune == '\r' || rune == '\n') | ||||||
|  |             { | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // Is it represented by a single byte? |             // Is it represented by a single byte? | ||||||
|             // In that case we don't have to calculate the |             // In that case we don't have to calculate the | ||||||
|             // actual cell width. |             // actual cell width. | ||||||
|   | |||||||
| @@ -6,11 +6,11 @@ namespace Spectre.Console.Internal | |||||||
| { | { | ||||||
|     internal static class MarkupParser |     internal static class MarkupParser | ||||||
|     { |     { | ||||||
|         public static Text Parse(string text, Style style = null) |         public static Text Parse(string text, Style? style = null) | ||||||
|         { |         { | ||||||
|             style ??= Style.Plain; |             style ??= Style.Plain; | ||||||
|  |  | ||||||
|             var result = new Text(string.Empty); |             var result = new Text(); | ||||||
|             using var tokenizer = new MarkupTokenizer(text); |             using var tokenizer = new MarkupTokenizer(text); | ||||||
|  |  | ||||||
|             var stack = new Stack<Style>(); |             var stack = new Stack<Style>(); | ||||||
| @@ -18,6 +18,10 @@ namespace Spectre.Console.Internal | |||||||
|             while (tokenizer.MoveNext()) |             while (tokenizer.MoveNext()) | ||||||
|             { |             { | ||||||
|                 var token = tokenizer.Current; |                 var token = tokenizer.Current; | ||||||
|  |                 if (token == null) | ||||||
|  |                 { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 if (token.Kind == MarkupTokenKind.Open) |                 if (token.Kind == MarkupTokenKind.Open) | ||||||
|                 { |                 { | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ namespace Spectre.Console.Internal | |||||||
|     { |     { | ||||||
|         private readonly StringBuffer _reader; |         private readonly StringBuffer _reader; | ||||||
|  |  | ||||||
|         public MarkupToken Current { get; private set; } |         public MarkupToken? Current { get; private set; } | ||||||
|  |  | ||||||
|         public MarkupTokenizer(string text) |         public MarkupTokenizer(string text) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.IO; | using System.IO; | ||||||
|  |  | ||||||
| namespace Spectre.Console.Internal | namespace Spectre.Console.Internal | ||||||
| { | { | ||||||
|     internal sealed class StringBuffer : IDisposable |     internal sealed class StringBuffer : IDisposable | ||||||
|     { |     { | ||||||
|  |         [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")] | ||||||
|         private readonly StringReader _reader; |         private readonly StringReader _reader; | ||||||
|         private readonly int _length; |         private readonly int _length; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Diagnostics.CodeAnalysis; | ||||||
|  | using System.Globalization; | ||||||
|  |  | ||||||
| namespace Spectre.Console.Internal | namespace Spectre.Console.Internal | ||||||
| { | { | ||||||
| @@ -12,16 +14,23 @@ namespace Spectre.Console.Internal | |||||||
|                 throw new InvalidOperationException(error); |                 throw new InvalidOperationException(error); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             if (style == null) | ||||||
|  |             { | ||||||
|  |                 // This should not happen, but we need to please the compiler | ||||||
|  |                 // which cannot know that style isn't null here. | ||||||
|  |                 throw new InvalidOperationException("Could not parse style."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             return style; |             return style; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static bool TryParse(string text, out Style style) |         public static bool TryParse(string text, out Style? style) | ||||||
|         { |         { | ||||||
|             style = Parse(text, out var error); |             style = Parse(text, out var error); | ||||||
|             return error == null; |             return error == null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private static Style Parse(string text, out string error) |         private static Style? Parse(string text, out string? error) | ||||||
|         { |         { | ||||||
|             var effectiveDecoration = (Decoration?)null; |             var effectiveDecoration = (Decoration?)null; | ||||||
|             var effectiveForeground = (Color?)null; |             var effectiveForeground = (Color?)null; | ||||||
| @@ -57,16 +66,30 @@ namespace Spectre.Console.Internal | |||||||
|                     var color = ColorTable.GetColor(part); |                     var color = ColorTable.GetColor(part); | ||||||
|                     if (color == null) |                     if (color == null) | ||||||
|                     { |                     { | ||||||
|                         if (!foreground) |                         if (part.StartsWith("#", StringComparison.OrdinalIgnoreCase)) | ||||||
|                         { |                         { | ||||||
|                             error = $"Could not find color '{part}'."; |                             color = ParseHexColor(part, out error); | ||||||
|  |                             if (!string.IsNullOrWhiteSpace(error)) | ||||||
|  |                             { | ||||||
|  |                                 return null; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         else if (part.StartsWith("rgb", StringComparison.OrdinalIgnoreCase)) | ||||||
|  |                         { | ||||||
|  |                             color = ParseRgbColor(part, out error); | ||||||
|  |                             if (!string.IsNullOrWhiteSpace(error)) | ||||||
|  |                             { | ||||||
|  |                                 return null; | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                         else |                         else | ||||||
|                         { |                         { | ||||||
|                             error = $"Could not find color or style '{part}'."; |                             error = !foreground | ||||||
|                         } |                                 ? $"Could not find color '{part}'." | ||||||
|  |                                 : $"Could not find color or style '{part}'."; | ||||||
|  |  | ||||||
|                         return null; |                             return null; | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     if (foreground) |                     if (foreground) | ||||||
| @@ -95,5 +118,82 @@ namespace Spectre.Console.Internal | |||||||
|             error = null; |             error = null; | ||||||
|             return new Style(effectiveForeground, effectiveBackground, effectiveDecoration); |             return new Style(effectiveForeground, effectiveBackground, effectiveDecoration); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         [SuppressMessage("Design", "CA1031:Do not catch general exception types")] | ||||||
|  |         private static Color? ParseHexColor(string hex, out string? error) | ||||||
|  |         { | ||||||
|  |             error = null; | ||||||
|  |  | ||||||
|  |             hex ??= string.Empty; | ||||||
|  |             hex = hex.Replace("#", string.Empty).Trim(); | ||||||
|  |  | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (!string.IsNullOrWhiteSpace(hex)) | ||||||
|  |                 { | ||||||
|  |                     if (hex.Length == 6) | ||||||
|  |                     { | ||||||
|  |                         return new Color( | ||||||
|  |                             (byte)Convert.ToUInt32(hex.Substring(0, 2), 16), | ||||||
|  |                             (byte)Convert.ToUInt32(hex.Substring(2, 2), 16), | ||||||
|  |                             (byte)Convert.ToUInt32(hex.Substring(4, 2), 16)); | ||||||
|  |                     } | ||||||
|  |                     else if (hex.Length == 3) | ||||||
|  |                     { | ||||||
|  |                         return new Color( | ||||||
|  |                             (byte)Convert.ToUInt32(new string(hex[0], 2), 16), | ||||||
|  |                             (byte)Convert.ToUInt32(new string(hex[1], 2), 16), | ||||||
|  |                             (byte)Convert.ToUInt32(new string(hex[2], 2), 16)); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 error = $"Invalid hex color '#{hex}'. {ex.Message}"; | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             error = $"Invalid hex color '#{hex}'."; | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [SuppressMessage("Design", "CA1031:Do not catch general exception types")] | ||||||
|  |         private static Color? ParseRgbColor(string rgb, out string? error) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 error = null; | ||||||
|  |  | ||||||
|  |                 var normalized = rgb ?? string.Empty; | ||||||
|  |                 if (normalized.Length >= 3) | ||||||
|  |                 { | ||||||
|  |                     // Trim parenthesises | ||||||
|  |                     normalized = normalized.Substring(3).Trim(); | ||||||
|  |  | ||||||
|  |                     if (normalized.StartsWith("(", StringComparison.OrdinalIgnoreCase) && | ||||||
|  |                        normalized.EndsWith(")", StringComparison.OrdinalIgnoreCase)) | ||||||
|  |                     { | ||||||
|  |                         normalized = normalized.Trim('(').Trim(')'); | ||||||
|  |  | ||||||
|  |                         var parts = normalized.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | ||||||
|  |                         if (parts.Length == 3) | ||||||
|  |                         { | ||||||
|  |                             return new Color( | ||||||
|  |                                 (byte)Convert.ToInt32(parts[0], CultureInfo.InvariantCulture), | ||||||
|  |                                 (byte)Convert.ToInt32(parts[1], CultureInfo.InvariantCulture), | ||||||
|  |                                 (byte)Convert.ToInt32(parts[2], CultureInfo.InvariantCulture)); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 error = $"Invalid RGB color '{rgb}'. {ex.Message}"; | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             error = $"Invalid RGB color '{rgb}'."; | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										75
									
								
								src/Spectre.Console/Internal/Utilities/Ratio.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/Spectre.Console/Internal/Utilities/Ratio.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | // Ported from Rich by Will McGugan, licensed under MIT. | ||||||
|  | // https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/_ratio.py | ||||||
|  |  | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Diagnostics; | ||||||
|  | using System.Linq; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Internal | ||||||
|  | { | ||||||
|  |     internal static class Ratio | ||||||
|  |     { | ||||||
|  |         public static List<int> Reduce(int total, List<int> ratios, List<int> maximums, List<int> values) | ||||||
|  |         { | ||||||
|  |             ratios = ratios.Zip(maximums, (a, b) => (ratio: a, max: b)).Select(a => a.max > 0 ? a.ratio : 0).ToList(); | ||||||
|  |             var totalRatio = ratios.Sum(); | ||||||
|  |             if (totalRatio <= 0) | ||||||
|  |             { | ||||||
|  |                 return values; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var totalRemaining = total; | ||||||
|  |             var result = new List<int>(); | ||||||
|  |  | ||||||
|  |             foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values)) | ||||||
|  |             { | ||||||
|  |                 if (ratio != 0 && totalRatio > 0) | ||||||
|  |                 { | ||||||
|  |                     var distributed = (int)Math.Min(maximum, Math.Round((double)(ratio * totalRemaining / totalRatio))); | ||||||
|  |                     result.Add(value - distributed); | ||||||
|  |                     totalRemaining -= distributed; | ||||||
|  |                     totalRatio -= ratio; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     result.Add(value); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static List<int> Distribute(int total, List<int> ratios, List<int>? minimums = null) | ||||||
|  |         { | ||||||
|  |             if (minimums != null) | ||||||
|  |             { | ||||||
|  |                 ratios = ratios.Zip(minimums, (a, b) => (ratio: a, min: b)).Select(a => a.min > 0 ? a.ratio : 0).ToList(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var totalRatio = ratios.Sum(); | ||||||
|  |             Debug.Assert(totalRatio > 0, "Sum or ratios must be > 0"); | ||||||
|  |  | ||||||
|  |             var totalRemaining = total; | ||||||
|  |             var distributedTotal = new List<int>(); | ||||||
|  |  | ||||||
|  |             if (minimums == null) | ||||||
|  |             { | ||||||
|  |                 minimums = ratios.Select(_ => 0).ToList(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var (ratio, minimum) in ratios.Zip(minimums, (a, b) => (a, b))) | ||||||
|  |             { | ||||||
|  |                 var distributed = (totalRatio > 0) | ||||||
|  |                     ? Math.Max(minimum, (int)Math.Ceiling(ratio * totalRemaining / (double)totalRatio)) | ||||||
|  |                     : totalRemaining; | ||||||
|  |  | ||||||
|  |                 distributedTotal.Add(distributed); | ||||||
|  |                 totalRatio -= ratio; | ||||||
|  |                 totalRemaining -= distributed; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return distributedTotal; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								src/Spectre.Console/Rendering/AlignableExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/Spectre.Console/Rendering/AlignableExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Contains extension methods for <see cref="IAlignable"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class AlignableExtensions | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Sets the alignment for an <see cref="IAlignable"/> object. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <typeparam name="T">The alignable type.</typeparam> | ||||||
|  |         /// <param name="alignable">The alignable object.</param> | ||||||
|  |         /// <param name="alignment">The alignment.</param> | ||||||
|  |         /// <returns>The same alignable object.</returns> | ||||||
|  |         public static T WithAlignment<T>(this T alignable, Justify alignment) | ||||||
|  |             where T : IAlignable | ||||||
|  |         { | ||||||
|  |             alignable.Alignment = alignment; | ||||||
|  |             return alignable; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Sets the <see cref="IAlignable"/> object to be left aligned. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <typeparam name="T">The alignable type.</typeparam> | ||||||
|  |         /// <param name="alignable">The alignable object.</param> | ||||||
|  |         /// <returns>The same alignable object.</returns> | ||||||
|  |         public static T LeftAligned<T>(this T alignable) | ||||||
|  |             where T : IAlignable | ||||||
|  |         { | ||||||
|  |             alignable.Alignment = Justify.Left; | ||||||
|  |             return alignable; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Sets the <see cref="IAlignable"/> object to be centered. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <typeparam name="T">The alignable type.</typeparam> | ||||||
|  |         /// <param name="alignable">The alignable object.</param> | ||||||
|  |         /// <returns>The same alignable object.</returns> | ||||||
|  |         public static T Centered<T>(this T alignable) | ||||||
|  |             where T : IAlignable | ||||||
|  |         { | ||||||
|  |             alignable.Alignment = Justify.Center; | ||||||
|  |             return alignable; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Sets the <see cref="IAlignable"/> object to be right aligned. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <typeparam name="T">The alignable type.</typeparam> | ||||||
|  |         /// <param name="alignable">The alignable object.</param> | ||||||
|  |         /// <returns>The same alignable object.</returns> | ||||||
|  |         public static T RightAligned<T>(this T alignable) | ||||||
|  |             where T : IAlignable | ||||||
|  |         { | ||||||
|  |             alignable.Alignment = Justify.Right; | ||||||
|  |             return alignable; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,7 +3,7 @@ using System.Collections.Generic; | |||||||
| using System.Globalization; | using System.Globalization; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| 
 | 
 | ||||||
| namespace Spectre.Console.Composition | namespace Spectre.Console.Rendering | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Represents a border used by tables. |     /// Represents a border used by tables. | ||||||
| @@ -17,6 +17,12 @@ namespace Spectre.Console.Composition | |||||||
|             { BorderKind.None, new NoBorder() }, |             { BorderKind.None, new NoBorder() }, | ||||||
|             { BorderKind.Ascii, new AsciiBorder() }, |             { BorderKind.Ascii, new AsciiBorder() }, | ||||||
|             { BorderKind.Square, new SquareBorder() }, |             { BorderKind.Square, new SquareBorder() }, | ||||||
|  |             { BorderKind.Rounded, new RoundedBorder() }, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         private static readonly Dictionary<BorderKind, BorderKind> _safeLookup = new Dictionary<BorderKind, BorderKind> | ||||||
|  |         { | ||||||
|  |             { BorderKind.Rounded, BorderKind.Square }, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -31,9 +37,15 @@ namespace Spectre.Console.Composition | |||||||
|         /// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>. |         /// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <param name="kind">The kind of border to get.</param> |         /// <param name="kind">The kind of border to get.</param> | ||||||
|  |         /// <param name="safe">Whether or not to get a "safe" border that can be rendered in a legacy console.</param> | ||||||
|         /// <returns>A <see cref="Border"/> instance representing the specified <see cref="BorderKind"/>.</returns> |         /// <returns>A <see cref="Border"/> instance representing the specified <see cref="BorderKind"/>.</returns> | ||||||
|         public static Border GetBorder(BorderKind kind) |         public static Border GetBorder(BorderKind kind, bool safe) | ||||||
|         { |         { | ||||||
|  |             if (safe && _safeLookup.TryGetValue(kind, out var safeKind)) | ||||||
|  |             { | ||||||
|  |                 kind = safeKind; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             if (!_borders.TryGetValue(kind, out var border)) |             if (!_borders.TryGetValue(kind, out var border)) | ||||||
|             { |             { | ||||||
|                 throw new InvalidOperationException("Unknown border kind"); |                 throw new InvalidOperationException("Unknown border kind"); | ||||||
| @@ -45,15 +57,20 @@ namespace Spectre.Console.Composition | |||||||
|         private Dictionary<BorderPart, string> Initialize() |         private Dictionary<BorderPart, string> Initialize() | ||||||
|         { |         { | ||||||
|             var lookup = new Dictionary<BorderPart, string>(); |             var lookup = new Dictionary<BorderPart, string>(); | ||||||
|             foreach (BorderPart part in Enum.GetValues(typeof(BorderPart))) |             foreach (BorderPart? part in Enum.GetValues(typeof(BorderPart))) | ||||||
|             { |             { | ||||||
|                 var text = GetBoxPart(part); |                 if (part == null) | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 var text = GetBoxPart(part.Value); | ||||||
|                 if (text.Length > 1) |                 if (text.Length > 1) | ||||||
|                 { |                 { | ||||||
|                     throw new InvalidOperationException("A box part cannot contain more than one character."); |                     throw new InvalidOperationException("A box part cannot contain more than one character."); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 lookup.Add(part, GetBoxPart(part)); |                 lookup.Add(part.Value, GetBoxPart(part.Value)); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return lookup; |             return lookup; | ||||||
| @@ -19,5 +19,10 @@ namespace Spectre.Console | |||||||
|         /// An old school ASCII border. |         /// An old school ASCII border. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         Ascii = 2, |         Ascii = 2, | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// A rounded border. | ||||||
|  |         /// </summary> | ||||||
|  |         Rounded = 3, | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| namespace Spectre.Console.Composition | namespace Spectre.Console.Rendering | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Represents the different border parts. |     /// Represents the different border parts. | ||||||
| @@ -73,7 +73,7 @@ namespace Spectre.Console.Composition | |||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// The right part of a cell. |         /// The right part of a cell. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         ColumnRight, |         CellRight, | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// The bottom left part of a footer. |         /// The bottom left part of a footer. | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| using System; | using System; | ||||||
| 
 | 
 | ||||||
| namespace Spectre.Console.Composition | namespace Spectre.Console.Rendering | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Represents an old school ASCII border. |     /// Represents an old school ASCII border. | ||||||
| @@ -25,7 +25,7 @@ namespace Spectre.Console.Composition | |||||||
|                 BorderPart.HeaderBottomRight => "|", |                 BorderPart.HeaderBottomRight => "|", | ||||||
|                 BorderPart.CellLeft => "|", |                 BorderPart.CellLeft => "|", | ||||||
|                 BorderPart.CellSeparator => "|", |                 BorderPart.CellSeparator => "|", | ||||||
|                 BorderPart.ColumnRight => "|", |                 BorderPart.CellRight => "|", | ||||||
|                 BorderPart.FooterBottomLeft => "+", |                 BorderPart.FooterBottomLeft => "+", | ||||||
|                 BorderPart.FooterBottom => "-", |                 BorderPart.FooterBottom => "-", | ||||||
|                 BorderPart.FooterBottomSeparator => "-", |                 BorderPart.FooterBottomSeparator => "-", | ||||||
							
								
								
									
										14
									
								
								src/Spectre.Console/Rendering/Borders/NoBorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Spectre.Console/Rendering/Borders/NoBorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents an invisible border. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class NoBorder : Border | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override string GetBoxPart(BorderPart part) | ||||||
|  |         { | ||||||
|  |             return " "; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								src/Spectre.Console/Rendering/Borders/RoundedBorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Spectre.Console/Rendering/Borders/RoundedBorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a rounded border. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class RoundedBorder : Border | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override string GetBoxPart(BorderPart part) | ||||||
|  |         { | ||||||
|  |             return part switch | ||||||
|  |             { | ||||||
|  |                 BorderPart.HeaderTopLeft => "╭", | ||||||
|  |                 BorderPart.HeaderTop => "─", | ||||||
|  |                 BorderPart.HeaderTopSeparator => "┬", | ||||||
|  |                 BorderPart.HeaderTopRight => "╮", | ||||||
|  |                 BorderPart.HeaderLeft => "│", | ||||||
|  |                 BorderPart.HeaderSeparator => "│", | ||||||
|  |                 BorderPart.HeaderRight => "│", | ||||||
|  |                 BorderPart.HeaderBottomLeft => "├", | ||||||
|  |                 BorderPart.HeaderBottom => "─", | ||||||
|  |                 BorderPart.HeaderBottomSeparator => "┼", | ||||||
|  |                 BorderPart.HeaderBottomRight => "┤", | ||||||
|  |                 BorderPart.CellLeft => "│", | ||||||
|  |                 BorderPart.CellSeparator => "│", | ||||||
|  |                 BorderPart.CellRight => "│", | ||||||
|  |                 BorderPart.FooterBottomLeft => "╰", | ||||||
|  |                 BorderPart.FooterBottom => "─", | ||||||
|  |                 BorderPart.FooterBottomSeparator => "┴", | ||||||
|  |                 BorderPart.FooterBottomRight => "╯", | ||||||
|  |                 _ => throw new InvalidOperationException("Unknown box part."), | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| using System; | using System; | ||||||
| 
 | 
 | ||||||
| namespace Spectre.Console.Composition | namespace Spectre.Console.Rendering | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Represents a square border. |     /// Represents a square border. | ||||||
| @@ -25,7 +25,7 @@ namespace Spectre.Console.Composition | |||||||
|                 BorderPart.HeaderBottomRight => "┤", |                 BorderPart.HeaderBottomRight => "┤", | ||||||
|                 BorderPart.CellLeft => "│", |                 BorderPart.CellLeft => "│", | ||||||
|                 BorderPart.CellSeparator => "│", |                 BorderPart.CellSeparator => "│", | ||||||
|                 BorderPart.ColumnRight => "│", |                 BorderPart.CellRight => "│", | ||||||
|                 BorderPart.FooterBottomLeft => "└", |                 BorderPart.FooterBottomLeft => "└", | ||||||
|                 BorderPart.FooterBottom => "─", |                 BorderPart.FooterBottom => "─", | ||||||
|                 BorderPart.FooterBottomSeparator => "┴", |                 BorderPart.FooterBottomSeparator => "┴", | ||||||
							
								
								
									
										141
									
								
								src/Spectre.Console/Rendering/Grid.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/Spectre.Console/Rendering/Grid.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using Spectre.Console.Internal; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// A renderable grid. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class Grid : Renderable | ||||||
|  |     { | ||||||
|  |         private readonly Table _table; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Grid"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         public Grid() | ||||||
|  |         { | ||||||
|  |             _table = new Table | ||||||
|  |             { | ||||||
|  |                 Border = BorderKind.None, | ||||||
|  |                 ShowHeaders = false, | ||||||
|  |                 IsGrid = true, | ||||||
|  |                 PadRightCell = false, | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             return ((IRenderable)_table).Measure(context, maxWidth); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override IEnumerable<Segment> Render(RenderContext context, int width) | ||||||
|  |         { | ||||||
|  |             return ((IRenderable)_table).Render(context, width); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a column to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         public void AddColumn() | ||||||
|  |         { | ||||||
|  |             AddColumn(new GridColumn()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a column to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="column">The column to add.</param> | ||||||
|  |         public void AddColumn(GridColumn column) | ||||||
|  |         { | ||||||
|  |             if (column is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(column)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (_table.RowCount > 0) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("Cannot add new columns to grid with existing rows."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Only pad the most right cell if we've explicitly set a padding. | ||||||
|  |             _table.PadRightCell = column.Padding != null; | ||||||
|  |  | ||||||
|  |             _table.AddColumn(new TableColumn(string.Empty) | ||||||
|  |             { | ||||||
|  |                 Width = column.Width, | ||||||
|  |                 NoWrap = column.NoWrap, | ||||||
|  |                 Padding = column.Padding ?? new Padding(0, 2), | ||||||
|  |                 Alignment = column.Alignment, | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a column to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="count">The number of columns to add.</param> | ||||||
|  |         public void AddColumns(int count) | ||||||
|  |         { | ||||||
|  |             for (var index = 0; index < count; index++) | ||||||
|  |             { | ||||||
|  |                 AddColumn(new GridColumn()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a column to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="columns">The columns to add.</param> | ||||||
|  |         public void AddColumns(params GridColumn[] columns) | ||||||
|  |         { | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var column in columns) | ||||||
|  |             { | ||||||
|  |                 AddColumn(column); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds an empty row to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         public void AddEmptyRow() | ||||||
|  |         { | ||||||
|  |             var columns = new IRenderable[_table.ColumnCount]; | ||||||
|  |             Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = Text.Empty); | ||||||
|  |             AddRow(columns); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a new row to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="columns">The columns to add.</param> | ||||||
|  |         public void AddRow(params IRenderable[] columns) | ||||||
|  |         { | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns.Length < _table.ColumnCount) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("The number of row columns are less than the number of grid columns."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns.Length > _table.ColumnCount) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("The number of row columns are greater than the number of grid columns."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _table.AddRow(columns); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								src/Spectre.Console/Rendering/GridColumn.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Spectre.Console/Rendering/GridColumn.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a grid column. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class GridColumn : IAlignable | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the width of the column. | ||||||
|  |         /// If <c>null</c>, the column will adapt to it's contents. | ||||||
|  |         /// </summary> | ||||||
|  |         public int? Width { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether wrapping of | ||||||
|  |         /// text within the column should be prevented. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool NoWrap { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the padding of the column. | ||||||
|  |         /// </summary> | ||||||
|  |         public Padding? Padding { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the alignment of the column. | ||||||
|  |         /// </summary> | ||||||
|  |         public Justify? Alignment { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								src/Spectre.Console/Rendering/GridExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/Spectre.Console/Rendering/GridExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | using System; | ||||||
|  | using System.Linq; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Contains extension methods for <see cref="Grid"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class GridExtensions | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a new row to the grid. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="grid">The grid to add the row to.</param> | ||||||
|  |         /// <param name="columns">The columns to add.</param> | ||||||
|  |         public static void AddRow(this Grid grid, params string[] columns) | ||||||
|  |         { | ||||||
|  |             if (grid is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(grid)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             grid.AddRow(columns.Select(column => new Markup(column)).ToArray()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								src/Spectre.Console/Rendering/IAlignable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/Spectre.Console/Rendering/IAlignable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents something that is alignable. | ||||||
|  |     /// </summary> | ||||||
|  |     public interface IAlignable | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the alignment. | ||||||
|  |         /// </summary> | ||||||
|  |         Justify? Alignment { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Text; |  | ||||||
| 
 | 
 | ||||||
| namespace Spectre.Console.Composition | namespace Spectre.Console.Rendering | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Represents something that can be rendered to the console. |     /// Represents something that can be rendered to the console. | ||||||
| @@ -11,17 +10,17 @@ namespace Spectre.Console.Composition | |||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Measures the renderable object. |         /// Measures the renderable object. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <param name="encoding">The encoding to use.</param> |         /// <param name="context">The render context.</param> | ||||||
|         /// <param name="maxWidth">The maximum allowed width.</param> |         /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|         /// <returns>The width of the object.</returns> |         /// <returns>The minimum and maximum width of the object.</returns> | ||||||
|         int Measure(Encoding encoding, int maxWidth); |         Measurement Measure(RenderContext context, int maxWidth); | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Renders the object. |         /// Renders the object. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <param name="encoding">The encoding to use.</param> |         /// <param name="context">The render context.</param> | ||||||
|         /// <param name="width">The width of the render area.</param> |         /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|         /// <returns>A collection of segments.</returns> |         /// <returns>A collection of segments.</returns> | ||||||
|         IEnumerable<Segment> Render(Encoding encoding, int width); |         IEnumerable<Segment> Render(RenderContext context, int maxWidth); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -16,7 +16,7 @@ namespace Spectre.Console | |||||||
|         Right = 1, |         Right = 1, | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Centered |         /// Centered. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         Center = 2, |         Center = 2, | ||||||
|     } |     } | ||||||
							
								
								
									
										43
									
								
								src/Spectre.Console/Rendering/Markup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/Spectre.Console/Rendering/Markup.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using Spectre.Console.Internal; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// A renderable piece of markup text. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class Markup : Renderable, IAlignable | ||||||
|  |     { | ||||||
|  |         private readonly Text _text; | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public Justify? Alignment | ||||||
|  |         { | ||||||
|  |             get => _text.Alignment; | ||||||
|  |             set => _text.Alignment = value; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Markup"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="text">The markup text.</param> | ||||||
|  |         /// <param name="style">The style of the text.</param> | ||||||
|  |         public Markup(string text, Style? style = null) | ||||||
|  |         { | ||||||
|  |             _text = MarkupParser.Parse(text, style); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             return ((IRenderable)_text).Measure(context, maxWidth); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             return ((IRenderable)_text).Render(context, maxWidth); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								src/Spectre.Console/Rendering/Measurement.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/Spectre.Console/Rendering/Measurement.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a measurement. | ||||||
|  |     /// </summary> | ||||||
|  |     public struct Measurement : IEquatable<Measurement> | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the minimum width. | ||||||
|  |         /// </summary> | ||||||
|  |         public int Min { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the maximum width. | ||||||
|  |         /// </summary> | ||||||
|  |         public int Max { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Measurement"/> struct. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="min">The minimum width.</param> | ||||||
|  |         /// <param name="max">The maximum width.</param> | ||||||
|  |         public Measurement(int min, int max) | ||||||
|  |         { | ||||||
|  |             Min = min; | ||||||
|  |             Max = max; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public override bool Equals(object? obj) | ||||||
|  |         { | ||||||
|  |             return obj is Measurement measurement && Equals(measurement); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public override int GetHashCode() | ||||||
|  |         { | ||||||
|  |             unchecked | ||||||
|  |             { | ||||||
|  |                 var hash = (int)2166136261; | ||||||
|  |                 hash = (hash * 16777619) ^ Min.GetHashCode(); | ||||||
|  |                 hash = (hash * 16777619) ^ Max.GetHashCode(); | ||||||
|  |                 return hash; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public bool Equals(Measurement other) | ||||||
|  |         { | ||||||
|  |             return Min == other.Min && Max == other.Max; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Checks if two <see cref="Measurement"/> instances are equal. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="left">The first measurement instance to compare.</param> | ||||||
|  |         /// <param name="right">The second measurement instance to compare.</param> | ||||||
|  |         /// <returns><c>true</c> if the two measurements are equal, otherwise <c>false</c>.</returns> | ||||||
|  |         public static bool operator ==(Measurement left, Measurement right) | ||||||
|  |         { | ||||||
|  |             return left.Equals(right); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Checks if two <see cref="Measurement"/> instances are not equal. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="left">The first measurement instance to compare.</param> | ||||||
|  |         /// <param name="right">The second measurement instance to compare.</param> | ||||||
|  |         /// <returns><c>true</c> if the two measurements are not equal, otherwise <c>false</c>.</returns> | ||||||
|  |         public static bool operator !=(Measurement left, Measurement right) | ||||||
|  |         { | ||||||
|  |             return !(left == right); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								src/Spectre.Console/Rendering/Padding.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/Spectre.Console/Rendering/Padding.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | using System; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a measurement. | ||||||
|  |     /// </summary> | ||||||
|  |     public struct Padding : IEquatable<Padding> | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the left padding. | ||||||
|  |         /// </summary> | ||||||
|  |         public int Left { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the right padding. | ||||||
|  |         /// </summary> | ||||||
|  |         public int Right { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Padding"/> struct. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="left">The left padding.</param> | ||||||
|  |         /// <param name="right">The right padding.</param> | ||||||
|  |         public Padding(int left, int right) | ||||||
|  |         { | ||||||
|  |             Left = left; | ||||||
|  |             Right = right; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public override bool Equals(object? obj) | ||||||
|  |         { | ||||||
|  |             return obj is Padding padding && Equals(padding); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public override int GetHashCode() | ||||||
|  |         { | ||||||
|  |             unchecked | ||||||
|  |             { | ||||||
|  |                 var hash = (int)2166136261; | ||||||
|  |                 hash = (hash * 16777619) ^ Left.GetHashCode(); | ||||||
|  |                 hash = (hash * 16777619) ^ Right.GetHashCode(); | ||||||
|  |                 return hash; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         public bool Equals(Padding other) | ||||||
|  |         { | ||||||
|  |             return Left == other.Left && Right == other.Right; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Checks if two <see cref="Padding"/> instances are equal. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="left">The first <see cref="Padding"/> instance to compare.</param> | ||||||
|  |         /// <param name="right">The second <see cref="Padding"/> instance to compare.</param> | ||||||
|  |         /// <returns><c>true</c> if the two instances are equal, otherwise <c>false</c>.</returns> | ||||||
|  |         public static bool operator ==(Padding left, Padding right) | ||||||
|  |         { | ||||||
|  |             return left.Equals(right); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Checks if two <see cref="Padding"/> instances are not equal. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="left">The first <see cref="Padding"/> instance to compare.</param> | ||||||
|  |         /// <param name="right">The second <see cref="Padding"/> instance to compare.</param> | ||||||
|  |         /// <returns><c>true</c> if the two instances are not equal, otherwise <c>false</c>.</returns> | ||||||
|  |         public static bool operator !=(Padding left, Padding right) | ||||||
|  |         { | ||||||
|  |             return !(left == right); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the horizontal padding. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>The horizontal padding.</returns> | ||||||
|  |         public int GetHorizontalPadding() | ||||||
|  |         { | ||||||
|  |             return Left + Right; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								src/Spectre.Console/Rendering/Panel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/Spectre.Console/Rendering/Panel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  | using SpectreBorder = Spectre.Console.Rendering.Border; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// A renderable panel. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class Panel : Renderable | ||||||
|  |     { | ||||||
|  |         private const int EdgeWidth = 2; | ||||||
|  |  | ||||||
|  |         private readonly IRenderable _child; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether or not to use | ||||||
|  |         /// a "safe" border on legacy consoles that might not be able | ||||||
|  |         /// to render non-ASCII characters. Defaults to <c>true</c>. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool SafeBorder { get; set; } = true; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the kind of border to use. | ||||||
|  |         /// </summary> | ||||||
|  |         public BorderKind Border { get; set; } = BorderKind.Square; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the alignment of the panel contents. | ||||||
|  |         /// </summary> | ||||||
|  |         public Justify? Alignment { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether or not the panel should | ||||||
|  |         /// fit the available space. If <c>false</c>, the panel width will be | ||||||
|  |         /// auto calculated. Defaults to <c>false</c>. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool Expand { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the padding. | ||||||
|  |         /// </summary> | ||||||
|  |         public Padding Padding { get; set; } = new Padding(1, 1); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Panel"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="text">The panel content.</param> | ||||||
|  |         public Panel(string text) | ||||||
|  |             : this(new Markup(text)) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Panel"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="content">The panel content.</param> | ||||||
|  |         public Panel(IRenderable content) | ||||||
|  |         { | ||||||
|  |             _child = content ?? throw new System.ArgumentNullException(nameof(content)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             var childWidth = _child.Measure(context, maxWidth); | ||||||
|  |             return new Measurement( | ||||||
|  |                 childWidth.Min + 2 + Padding.GetHorizontalPadding(), | ||||||
|  |                 childWidth.Max + 2 + Padding.GetHorizontalPadding()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); | ||||||
|  |  | ||||||
|  |             var paddingWidth = Padding.GetHorizontalPadding(); | ||||||
|  |             var childWidth = maxWidth - EdgeWidth - paddingWidth; | ||||||
|  |  | ||||||
|  |             if (!Expand) | ||||||
|  |             { | ||||||
|  |                 var measurement = _child.Measure(context, maxWidth - EdgeWidth - paddingWidth); | ||||||
|  |                 childWidth = measurement.Max; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var panelWidth = childWidth + paddingWidth; | ||||||
|  |  | ||||||
|  |             // Panel top | ||||||
|  |             var result = new List<Segment> | ||||||
|  |             { | ||||||
|  |                 new Segment(border.GetPart(BorderPart.HeaderTopLeft)), | ||||||
|  |                 new Segment(border.GetPart(BorderPart.HeaderTop, panelWidth)), | ||||||
|  |                 new Segment(border.GetPart(BorderPart.HeaderTopRight)), | ||||||
|  |                 Segment.LineBreak, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             // Render the child. | ||||||
|  |             var childContext = context.WithJustification(Alignment); | ||||||
|  |             var childSegments = _child.Render(childContext, childWidth); | ||||||
|  |  | ||||||
|  |             // Split the child segments into lines. | ||||||
|  |             foreach (var line in Segment.SplitLines(childSegments, panelWidth)) | ||||||
|  |             { | ||||||
|  |                 result.Add(new Segment(border.GetPart(BorderPart.CellLeft))); | ||||||
|  |  | ||||||
|  |                 // Left padding | ||||||
|  |                 if (Padding.Left > 0) | ||||||
|  |                 { | ||||||
|  |                     result.Add(new Segment(new string(' ', Padding.Left))); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var content = new List<Segment>(); | ||||||
|  |                 content.AddRange(line); | ||||||
|  |  | ||||||
|  |                 // Do we need to pad the panel? | ||||||
|  |                 var length = line.Sum(segment => segment.CellLength(context.Encoding)); | ||||||
|  |                 if (length < childWidth) | ||||||
|  |                 { | ||||||
|  |                     var diff = childWidth - length; | ||||||
|  |                     content.Add(new Segment(new string(' ', diff))); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 result.AddRange(content); | ||||||
|  |  | ||||||
|  |                 // Right padding | ||||||
|  |                 if (Padding.Right > 0) | ||||||
|  |                 { | ||||||
|  |                     result.Add(new Segment(new string(' ', Padding.Right))); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 result.Add(new Segment(border.GetPart(BorderPart.CellRight))); | ||||||
|  |                 result.Add(Segment.LineBreak); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Panel bottom | ||||||
|  |             result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft))); | ||||||
|  |             result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, panelWidth))); | ||||||
|  |             result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight))); | ||||||
|  |             result.Add(Segment.LineBreak); | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								src/Spectre.Console/Rendering/RenderContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/Spectre.Console/Rendering/RenderContext.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a render context. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class RenderContext | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the console's output encoding. | ||||||
|  |         /// </summary> | ||||||
|  |         public Encoding Encoding { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets a value indicating whether or not this a legacy console (i.e. cmd.exe). | ||||||
|  |         /// </summary> | ||||||
|  |         public bool LegacyConsole { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets a value indicating whether or not unicode is supported. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool Unicode { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the current justification. | ||||||
|  |         /// </summary> | ||||||
|  |         public Justify? Justification { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="RenderContext"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="encoding">The console's output encoding.</param> | ||||||
|  |         /// <param name="legacyConsole">A value indicating whether or not this a legacy console (i.e. cmd.exe).</param> | ||||||
|  |         /// <param name="justification">The justification to use when rendering.</param> | ||||||
|  |         public RenderContext(Encoding encoding, bool legacyConsole, Justify? justification = null) | ||||||
|  |         { | ||||||
|  |             Encoding = encoding ?? throw new System.ArgumentNullException(nameof(encoding)); | ||||||
|  |             LegacyConsole = legacyConsole; | ||||||
|  |             Justification = justification; | ||||||
|  |             Unicode = Encoding == Encoding.UTF8 || Encoding == Encoding.Unicode; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Creates a new context with the specified justification. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="justification">The justification.</param> | ||||||
|  |         /// <returns>A new <see cref="RenderContext"/> instance with the specified justification.</returns> | ||||||
|  |         public RenderContext WithJustification(Justify? justification) | ||||||
|  |         { | ||||||
|  |             return new RenderContext(Encoding, LegacyConsole, justification); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								src/Spectre.Console/Rendering/Renderable.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/Spectre.Console/Rendering/Renderable.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Base class for a renderable object implementing <see cref="IRenderable"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     public abstract class Renderable : IRenderable | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         Measurement IRenderable.Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             return Measure(context, maxWidth); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         IEnumerable<Segment> IRenderable.Render(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             return Render(context, maxWidth); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Measures the renderable object. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="context">The render context.</param> | ||||||
|  |         /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|  |         /// <returns>The minimum and maximum width of the object.</returns> | ||||||
|  |         protected virtual Measurement Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             return new Measurement(maxWidth, maxWidth); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Renders the object. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="context">The render context.</param> | ||||||
|  |         /// <param name="maxWidth">The maximum allowed width.</param> | ||||||
|  |         /// <returns>A collection of segments.</returns> | ||||||
|  |         protected abstract IEnumerable<Segment> Render(RenderContext context, int maxWidth); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -5,7 +5,7 @@ using System.Linq; | |||||||
| using System.Text; | using System.Text; | ||||||
| using Spectre.Console.Internal; | using Spectre.Console.Internal; | ||||||
| 
 | 
 | ||||||
| namespace Spectre.Console.Composition | namespace Spectre.Console.Rendering | ||||||
| { | { | ||||||
|     /// <summary> |     /// <summary> | ||||||
|     /// Represents a renderable segment. |     /// Represents a renderable segment. | ||||||
| @@ -24,11 +24,28 @@ namespace Spectre.Console.Composition | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         public bool IsLineBreak { get; } |         public bool IsLineBreak { get; } | ||||||
| 
 | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets a value indicating whether or not this is a whitespace | ||||||
|  |         /// that should be preserved but not taken into account when | ||||||
|  |         /// layouting text. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool IsWhiteSpace { get; } | ||||||
|  | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Gets the segment style. |         /// Gets the segment style. | ||||||
|         /// </summary> |         /// </summary> | ||||||
|         public Style Style { get; } |         public Style Style { get; } | ||||||
| 
 | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets a segment representing a line break. | ||||||
|  |         /// </summary> | ||||||
|  |         public static Segment LineBreak { get; } = new Segment(Environment.NewLine, Style.Plain, true); | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets an empty segment. | ||||||
|  |         /// </summary> | ||||||
|  |         public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain); | ||||||
|  | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Initializes a new instance of the <see cref="Segment"/> class. |         /// Initializes a new instance of the <see cref="Segment"/> class. | ||||||
|         /// </summary> |         /// </summary> | ||||||
| @@ -50,18 +67,15 @@ namespace Spectre.Console.Composition | |||||||
| 
 | 
 | ||||||
|         private Segment(string text, Style style, bool lineBreak) |         private Segment(string text, Style style, bool lineBreak) | ||||||
|         { |         { | ||||||
|             Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text)); |             if (text is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(text)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Text = text.NormalizeLineEndings(); | ||||||
|             Style = style; |             Style = style; | ||||||
|             IsLineBreak = lineBreak; |             IsLineBreak = lineBreak; | ||||||
|         } |             IsWhiteSpace = string.IsNullOrWhiteSpace(text); | ||||||
| 
 |  | ||||||
|         /// <summary> |  | ||||||
|         /// Creates a segment that represents an implicit line break. |  | ||||||
|         /// </summary> |  | ||||||
|         /// <returns>A segment that represents an implicit line break.</returns> |  | ||||||
|         public static Segment LineBreak() |  | ||||||
|         { |  | ||||||
|             return new Segment("\n", Style.Plain, true); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -81,7 +95,7 @@ namespace Spectre.Console.Composition | |||||||
|         /// <returns>A new segment without any trailing line endings.</returns> |         /// <returns>A new segment without any trailing line endings.</returns> | ||||||
|         public Segment StripLineEndings() |         public Segment StripLineEndings() | ||||||
|         { |         { | ||||||
|             return new Segment(Text.TrimEnd('\n'), Style); |             return new Segment(Text.TrimEnd('\n').TrimEnd('\r'), Style); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /// <summary> |         /// <summary> | ||||||
| @@ -89,7 +103,7 @@ namespace Spectre.Console.Composition | |||||||
|         /// </summary> |         /// </summary> | ||||||
|         /// <param name="offset">The offset where to split the segment.</param> |         /// <param name="offset">The offset where to split the segment.</param> | ||||||
|         /// <returns>One or two new segments representing the split.</returns> |         /// <returns>One or two new segments representing the split.</returns> | ||||||
|         public (Segment First, Segment Second) Split(int offset) |         public (Segment First, Segment? Second) Split(int offset) | ||||||
|         { |         { | ||||||
|             if (offset < 0) |             if (offset < 0) | ||||||
|             { |             { | ||||||
| @@ -138,9 +152,9 @@ namespace Spectre.Console.Composition | |||||||
|             { |             { | ||||||
|                 var segment = stack.Pop(); |                 var segment = stack.Pop(); | ||||||
| 
 | 
 | ||||||
|                 if (line.Length + segment.Text.Length > maxWidth) |                 if (line.Width + segment.Text.Length > maxWidth) | ||||||
|                 { |                 { | ||||||
|                     var diff = -(maxWidth - (line.Length + segment.Text.Length)); |                     var diff = -(maxWidth - (line.Width + segment.Text.Length)); | ||||||
|                     var offset = segment.Text.Length - diff; |                     var offset = segment.Text.Length - diff; | ||||||
| 
 | 
 | ||||||
|                     var (first, second) = segment.Split(offset); |                     var (first, second) = segment.Split(offset); | ||||||
| @@ -161,7 +175,7 @@ namespace Spectre.Console.Composition | |||||||
|                 { |                 { | ||||||
|                     if (segment.Text == "\n") |                     if (segment.Text == "\n") | ||||||
|                     { |                     { | ||||||
|                         if (line.Length > 0 || segment.IsLineBreak) |                         if (line.Width > 0 || segment.IsLineBreak) | ||||||
|                         { |                         { | ||||||
|                             lines.Add(line); |                             lines.Add(line); | ||||||
|                             line = new SegmentLine(); |                             line = new SegmentLine(); | ||||||
| @@ -184,7 +198,7 @@ namespace Spectre.Console.Composition | |||||||
| 
 | 
 | ||||||
|                         if (parts.Length > 1) |                         if (parts.Length > 1) | ||||||
|                         { |                         { | ||||||
|                             if (line.Length > 0) |                             if (line.Width > 0) | ||||||
|                             { |                             { | ||||||
|                                 lines.Add(line); |                                 lines.Add(line); | ||||||
|                                 line = new SegmentLine(); |                                 line = new SegmentLine(); | ||||||
| @@ -211,5 +225,21 @@ namespace Spectre.Console.Composition | |||||||
| 
 | 
 | ||||||
|             return lines; |             return lines; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         internal static List<List<SegmentLine>> MakeSameHeight(int cellHeight, List<List<SegmentLine>> cells) | ||||||
|  |         { | ||||||
|  |             foreach (var cell in cells) | ||||||
|  |             { | ||||||
|  |                 if (cell.Count < cellHeight) | ||||||
|  |                 { | ||||||
|  |                     while (cell.Count != cellHeight) | ||||||
|  |                     { | ||||||
|  |                         cell.Add(new SegmentLine()); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return cells; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										38
									
								
								src/Spectre.Console/Rendering/SegmentLine.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/Spectre.Console/Rendering/SegmentLine.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Diagnostics.CodeAnalysis; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a collection of segments. | ||||||
|  |     /// </summary> | ||||||
|  |     [SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")] | ||||||
|  |     public sealed class SegmentLine : List<Segment> | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the width of the line. | ||||||
|  |         /// </summary> | ||||||
|  |         public int Width => this.Sum(line => line.Text.Length); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the cell width of the segment line. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="encoding">The encoding to use.</param> | ||||||
|  |         /// <returns>The cell width of the segment line.</returns> | ||||||
|  |         public int CellWidth(Encoding encoding) | ||||||
|  |         { | ||||||
|  |             return this.Sum(line => line.CellLength(encoding)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Preprends a segment to the line. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="segment">The segment to prepend.</param> | ||||||
|  |         public void Prepend(Segment segment) | ||||||
|  |         { | ||||||
|  |             Insert(0, segment); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/Spectre.Console/Rendering/SegmentLineEnumerator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Spectre.Console/Rendering/SegmentLineEnumerator.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | using System.Collections; | ||||||
|  | using System.Collections.Generic; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     internal sealed class SegmentLineEnumerator : IEnumerable<Segment> | ||||||
|  |     { | ||||||
|  |         private readonly List<SegmentLine> _lines; | ||||||
|  |  | ||||||
|  |         public SegmentLineEnumerator(List<SegmentLine> lines) | ||||||
|  |         { | ||||||
|  |             _lines = lines; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public IEnumerator<Segment> GetEnumerator() | ||||||
|  |         { | ||||||
|  |             return new SegmentLineIterator(_lines); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         IEnumerator IEnumerable.GetEnumerator() | ||||||
|  |         { | ||||||
|  |             return GetEnumerator(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										99
									
								
								src/Spectre.Console/Rendering/SegmentLineIterator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/Spectre.Console/Rendering/SegmentLineIterator.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | using System.Collections; | ||||||
|  | using System.Collections.Generic; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console.Rendering | ||||||
|  | { | ||||||
|  |     internal sealed class SegmentLineIterator : IEnumerator<Segment> | ||||||
|  |     { | ||||||
|  |         private readonly List<SegmentLine> _lines; | ||||||
|  |         private int _currentLine; | ||||||
|  |         private int _currentIndex; | ||||||
|  |         private bool _lineBreakEmitted; | ||||||
|  |  | ||||||
|  |         public Segment Current { get; private set; } | ||||||
|  |         object? IEnumerator.Current => Current; | ||||||
|  |  | ||||||
|  |         public SegmentLineIterator(List<SegmentLine> lines) | ||||||
|  |         { | ||||||
|  |             _currentLine = 0; | ||||||
|  |             _currentIndex = -1; | ||||||
|  |             _lines = lines; | ||||||
|  |  | ||||||
|  |             Current = Segment.Empty; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Dispose() | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool MoveNext() | ||||||
|  |         { | ||||||
|  |             if (_currentLine > _lines.Count - 1) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _currentIndex += 1; | ||||||
|  |  | ||||||
|  |             // Did we go past the end of the line? | ||||||
|  |             if (_currentIndex > _lines[_currentLine].Count - 1) | ||||||
|  |             { | ||||||
|  |                 // We haven't just emitted a line break? | ||||||
|  |                 if (!_lineBreakEmitted) | ||||||
|  |                 { | ||||||
|  |                     // Got any more lines? | ||||||
|  |                     if (_currentIndex + 1 > _lines[_currentLine].Count - 1) | ||||||
|  |                     { | ||||||
|  |                         // Only emit a line break if the next one isn't a line break. | ||||||
|  |                         if ((_currentLine + 1 <= _lines.Count - 1) | ||||||
|  |                             && _lines[_currentLine + 1].Count > 0 | ||||||
|  |                             && !_lines[_currentLine + 1][0].IsLineBreak) | ||||||
|  |                         { | ||||||
|  |                             _lineBreakEmitted = true; | ||||||
|  |                             Current = Segment.LineBreak; | ||||||
|  |                             return true; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Increase the line and reset the index. | ||||||
|  |                 _currentLine += 1; | ||||||
|  |                 _currentIndex = 0; | ||||||
|  |  | ||||||
|  |                 _lineBreakEmitted = false; | ||||||
|  |  | ||||||
|  |                 // No more lines? | ||||||
|  |                 if (_currentLine > _lines.Count - 1) | ||||||
|  |                 { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Nothing on the line? | ||||||
|  |                 while (_currentIndex > _lines[_currentLine].Count - 1) | ||||||
|  |                 { | ||||||
|  |                     _currentLine += 1; | ||||||
|  |                     _currentIndex = 0; | ||||||
|  |  | ||||||
|  |                     if (_currentLine > _lines.Count - 1) | ||||||
|  |                     { | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Reset the flag | ||||||
|  |             _lineBreakEmitted = false; | ||||||
|  |  | ||||||
|  |             Current = _lines[_currentLine][_currentIndex]; | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Reset() | ||||||
|  |         { | ||||||
|  |             _currentLine = 0; | ||||||
|  |             _currentIndex = -1; | ||||||
|  |  | ||||||
|  |             Current = Segment.Empty; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										125
									
								
								src/Spectre.Console/Rendering/Table.Calculations.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/Spectre.Console/Rendering/Table.Calculations.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using Spectre.Console.Internal; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a table. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed partial class Table | ||||||
|  |     { | ||||||
|  |         private const int EdgeCount = 2; | ||||||
|  |  | ||||||
|  |         // Calculate the widths of each column, including padding, not including borders. | ||||||
|  |         // Ported from Rich by Will McGugan, licensed under MIT. | ||||||
|  |         // https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L394 | ||||||
|  |         private List<int> CalculateColumnWidths(RenderContext options, int maxWidth) | ||||||
|  |         { | ||||||
|  |             var width_ranges = _columns.Select(column => MeasureColumn(column, options, maxWidth)); | ||||||
|  |             var widths = width_ranges.Select(range => range.Max).ToList(); | ||||||
|  |  | ||||||
|  |             var tableWidth = widths.Sum(); | ||||||
|  |  | ||||||
|  |             if (tableWidth > maxWidth) | ||||||
|  |             { | ||||||
|  |                 var wrappable = _columns.Select(c => !c.NoWrap).ToList(); | ||||||
|  |                 widths = CollapseWidths(widths, wrappable, maxWidth); | ||||||
|  |                 tableWidth = widths.Sum(); | ||||||
|  |  | ||||||
|  |                 // last resort, reduce columns evenly | ||||||
|  |                 if (tableWidth > maxWidth) | ||||||
|  |                 { | ||||||
|  |                     var excessWidth = tableWidth - maxWidth; | ||||||
|  |                     widths = Ratio.Reduce(excessWidth, widths.Select(_ => 1).ToList(), widths, widths); | ||||||
|  |                     tableWidth = widths.Sum(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (tableWidth < maxWidth && ShouldExpand()) | ||||||
|  |             { | ||||||
|  |                 var padWidths = Ratio.Distribute(maxWidth - tableWidth, widths); | ||||||
|  |                 widths = widths.Zip(padWidths, (a, b) => (a, b)).Select(f => f.a + f.b).ToList(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return widths; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Reduce widths so that the total is less or equal to the max width. | ||||||
|  |         // Ported from Rich by Will McGugan, licensed under MIT. | ||||||
|  |         // https://github.com/willmcgugan/rich/blob/527475837ebbfc427530b3ee0d4d0741d2d0fc6d/rich/table.py#L442 | ||||||
|  |         private static List<int> CollapseWidths(List<int> widths, List<bool> wrappable, int maxWidth) | ||||||
|  |         { | ||||||
|  |             var totalWidth = widths.Sum(); | ||||||
|  |             var excessWidth = totalWidth - maxWidth; | ||||||
|  |  | ||||||
|  |             if (wrappable.AnyTrue()) | ||||||
|  |             { | ||||||
|  |                 while (totalWidth != 0 && excessWidth > 0) | ||||||
|  |                 { | ||||||
|  |                     var maxColumn = widths.Zip(wrappable, (first, second) => (width: first, allowWrap: second)) | ||||||
|  |                         .Where(x => x.allowWrap) | ||||||
|  |                         .Max(x => x.width); | ||||||
|  |  | ||||||
|  |                     var secondMaxColumn = widths.Zip(wrappable, (width, allowWrap) => allowWrap && width != maxColumn ? width : 1).Max(); | ||||||
|  |                     var columnDifference = maxColumn - secondMaxColumn; | ||||||
|  |  | ||||||
|  |                     var ratios = widths.Zip(wrappable, (width, allowWrap) => width == maxColumn && allowWrap ? 1 : 0).ToList(); | ||||||
|  |                     if (!ratios.Any(x => x != 0) || columnDifference == 0) | ||||||
|  |                     { | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var maxReduce = widths.Select(_ => Math.Min(excessWidth, columnDifference)).ToList(); | ||||||
|  |                     widths = Ratio.Reduce(excessWidth, ratios, maxReduce, widths); | ||||||
|  |  | ||||||
|  |                     totalWidth = widths.Sum(); | ||||||
|  |                     excessWidth = totalWidth - maxWidth; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return widths; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private (int Min, int Max) MeasureColumn(TableColumn column, RenderContext options, int maxWidth) | ||||||
|  |         { | ||||||
|  |             var padding = column.Padding.GetHorizontalPadding(); | ||||||
|  |  | ||||||
|  |             // Predetermined width? | ||||||
|  |             if (column.Width != null) | ||||||
|  |             { | ||||||
|  |                 return (column.Width.Value + padding, column.Width.Value + padding); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var columnIndex = _columns.IndexOf(column); | ||||||
|  |             var rows = _rows.Select(row => row[columnIndex]); | ||||||
|  |  | ||||||
|  |             var minWidths = new List<int>(); | ||||||
|  |             var maxWidths = new List<int>(); | ||||||
|  |  | ||||||
|  |             // Include columns in measurement | ||||||
|  |             var measure = column.Text.Measure(options, maxWidth); | ||||||
|  |             minWidths.Add(measure.Min); | ||||||
|  |             maxWidths.Add(measure.Max); | ||||||
|  |  | ||||||
|  |             foreach (var row in rows) | ||||||
|  |             { | ||||||
|  |                 measure = row.Measure(options, maxWidth); | ||||||
|  |                 minWidths.Add(measure.Min); | ||||||
|  |                 maxWidths.Add(measure.Max); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return (minWidths.Count > 0 ? minWidths.Max() : padding, | ||||||
|  |                     maxWidths.Count > 0 ? maxWidths.Max() : maxWidth); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private int GetExtraWidth(bool includePadding) | ||||||
|  |         { | ||||||
|  |             var separators = _columns.Count - 1; | ||||||
|  |             var padding = includePadding ? _columns.Select(x => x.Padding.GetHorizontalPadding()).Sum() : 0; | ||||||
|  |             return separators + EdgeCount + padding; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										356
									
								
								src/Spectre.Console/Rendering/Table.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								src/Spectre.Console/Rendering/Table.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,356 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using Spectre.Console.Internal; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  | using SpectreBorder = Spectre.Console.Rendering.Border; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// A renderable table. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed partial class Table : Renderable | ||||||
|  |     { | ||||||
|  |         private readonly List<TableColumn> _columns; | ||||||
|  |         private readonly List<List<IRenderable>> _rows; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the number of columns in the table. | ||||||
|  |         /// </summary> | ||||||
|  |         public int ColumnCount => _columns.Count; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the number of rows in the table. | ||||||
|  |         /// </summary> | ||||||
|  |         public int RowCount => _rows.Count; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the kind of border to use. | ||||||
|  |         /// </summary> | ||||||
|  |         public BorderKind Border { get; set; } = BorderKind.Square; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether or not table headers should be shown. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool ShowHeaders { get; set; } = true; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether or not the table should | ||||||
|  |         /// fit the available space. If <c>false</c>, the table width will be | ||||||
|  |         /// auto calculated. Defaults to <c>false</c>. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool Expand { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the width of the table. | ||||||
|  |         /// </summary> | ||||||
|  |         public int? Width { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether or not to use | ||||||
|  |         /// a "safe" border on legacy consoles that might not be able | ||||||
|  |         /// to render non-ASCII characters. Defaults to <c>true</c>. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool SafeBorder { get; set; } = true; | ||||||
|  |  | ||||||
|  |         // Whether this is a grid or not. | ||||||
|  |         internal bool IsGrid { get; set; } | ||||||
|  |  | ||||||
|  |         // Whether or not the most right cell should be padded. | ||||||
|  |         // This is almost always the case, unless we're rendering | ||||||
|  |         // a grid without explicit padding in the last cell. | ||||||
|  |         internal bool PadRightCell { get; set; } = true; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Table"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         public Table() | ||||||
|  |         { | ||||||
|  |             _columns = new List<TableColumn>(); | ||||||
|  |             _rows = new List<List<IRenderable>>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a column to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="column">The column to add.</param> | ||||||
|  |         public void AddColumn(TableColumn column) | ||||||
|  |         { | ||||||
|  |             if (column is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(column)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (_rows.Count > 0) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("Cannot add new columns to table with existing rows."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _columns.Add(column); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds multiple columns to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="columns">The columns to add.</param> | ||||||
|  |         public void AddColumns(params TableColumn[] columns) | ||||||
|  |         { | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var column in columns) | ||||||
|  |             { | ||||||
|  |                 AddColumn(column); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds an empty row to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         public void AddEmptyRow() | ||||||
|  |         { | ||||||
|  |             var columns = new IRenderable[ColumnCount]; | ||||||
|  |             Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = Text.Empty); | ||||||
|  |             AddRow(columns); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a row to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="columns">The row columns to add.</param> | ||||||
|  |         public void AddRow(params IRenderable[] columns) | ||||||
|  |         { | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns.Length < _columns.Count) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("The number of row columns are less than the number of table columns."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns.Length > _columns.Count) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("The number of row columns are greater than the number of table columns."); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             _rows.Add(columns.ToList()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             if (context is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(context)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (Width != null) | ||||||
|  |             { | ||||||
|  |                 maxWidth = Math.Min(Width.Value, maxWidth); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             maxWidth -= GetExtraWidth(includePadding: true); | ||||||
|  |  | ||||||
|  |             var measurements = _columns.Select(column => MeasureColumn(column, context, maxWidth)).ToList(); | ||||||
|  |             var min = measurements.Sum(x => x.Min) + GetExtraWidth(includePadding: true); | ||||||
|  |             var max = Width ?? measurements.Sum(x => x.Max) + GetExtraWidth(includePadding: true); | ||||||
|  |  | ||||||
|  |             return new Measurement(min, max); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             if (context is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(context)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var border = SpectreBorder.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); | ||||||
|  |             var tableWidth = maxWidth; | ||||||
|  |  | ||||||
|  |             var showBorder = Border != BorderKind.None; | ||||||
|  |             var hideBorder = Border == BorderKind.None; | ||||||
|  |             var hasRows = _rows.Count > 0; | ||||||
|  |  | ||||||
|  |             if (Width != null) | ||||||
|  |             { | ||||||
|  |                 maxWidth = Math.Min(Width.Value, maxWidth); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             maxWidth -= GetExtraWidth(includePadding: true); | ||||||
|  |  | ||||||
|  |             // Calculate the column and table widths | ||||||
|  |             var columnWidths = CalculateColumnWidths(context, maxWidth); | ||||||
|  |  | ||||||
|  |             // Update the table width. | ||||||
|  |             tableWidth = columnWidths.Sum() + GetExtraWidth(includePadding: true); | ||||||
|  |  | ||||||
|  |             var rows = new List<List<IRenderable>>(); | ||||||
|  |             if (ShowHeaders) | ||||||
|  |             { | ||||||
|  |                 // Add columns to top of rows | ||||||
|  |                 rows.Add(new List<IRenderable>(_columns.Select(c => c.Text))); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Add rows. | ||||||
|  |             rows.AddRange(_rows); | ||||||
|  |  | ||||||
|  |             // Iterate all rows | ||||||
|  |             var result = new List<Segment>(); | ||||||
|  |             foreach (var (index, firstRow, lastRow, row) in rows.Enumerate()) | ||||||
|  |             { | ||||||
|  |                 var cellHeight = 1; | ||||||
|  |  | ||||||
|  |                 // Get the list of cells for the row and calculate the cell height | ||||||
|  |                 var cells = new List<List<SegmentLine>>(); | ||||||
|  |                 foreach (var (columnIndex, _, _, (rowWidth, cell)) in columnWidths.Zip(row).Enumerate()) | ||||||
|  |                 { | ||||||
|  |                     var justification = _columns[columnIndex].Alignment; | ||||||
|  |                     var childContext = context.WithJustification(justification); | ||||||
|  |  | ||||||
|  |                     var lines = Segment.SplitLines(cell.Render(childContext, rowWidth)); | ||||||
|  |                     cellHeight = Math.Max(cellHeight, lines.Count); | ||||||
|  |                     cells.Add(lines); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Show top of header? | ||||||
|  |                 if (firstRow && showBorder) | ||||||
|  |                 { | ||||||
|  |                     result.Add(new Segment(border.GetPart(BorderPart.HeaderTopLeft))); | ||||||
|  |                     foreach (var (columnIndex, _, lastColumn, columnWidth) in columnWidths.Enumerate()) | ||||||
|  |                     { | ||||||
|  |                         var padding = _columns[columnIndex].Padding; | ||||||
|  |  | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Left))); // Left padding | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, columnWidth))); | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.HeaderTop, padding.Right))); // Right padding | ||||||
|  |  | ||||||
|  |                         if (!lastColumn) | ||||||
|  |                         { | ||||||
|  |                             result.Add(new Segment(border.GetPart(BorderPart.HeaderTopSeparator))); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     result.Add(new Segment(border.GetPart(BorderPart.HeaderTopRight))); | ||||||
|  |                     result.Add(Segment.LineBreak); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Iterate through each cell row | ||||||
|  |                 foreach (var cellRowIndex in Enumerable.Range(0, cellHeight)) | ||||||
|  |                 { | ||||||
|  |                     // Make cells the same shape | ||||||
|  |                     cells = Segment.MakeSameHeight(cellHeight, cells); | ||||||
|  |  | ||||||
|  |                     foreach (var (cellIndex, firstCell, lastCell, cell) in cells.Enumerate()) | ||||||
|  |                     { | ||||||
|  |                         if (firstCell && showBorder) | ||||||
|  |                         { | ||||||
|  |                             // Show left column edge | ||||||
|  |                             result.Add(new Segment(border.GetPart(BorderPart.CellLeft))); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         // Pad column on left side. | ||||||
|  |                         if (showBorder || IsGrid) | ||||||
|  |                         { | ||||||
|  |                             var leftPadding = _columns[cellIndex].Padding.Left; | ||||||
|  |                             if (leftPadding > 0) | ||||||
|  |                             { | ||||||
|  |                                 result.Add(new Segment(new string(' ', leftPadding))); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         // Add content | ||||||
|  |                         result.AddRange(cell[cellRowIndex]); | ||||||
|  |  | ||||||
|  |                         // Pad cell content right | ||||||
|  |                         var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding)); | ||||||
|  |                         if (length < columnWidths[cellIndex]) | ||||||
|  |                         { | ||||||
|  |                             result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length))); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         // Pad column on the right side | ||||||
|  |                         if (showBorder || (hideBorder && !lastCell) || (hideBorder && lastCell && IsGrid && PadRightCell)) | ||||||
|  |                         { | ||||||
|  |                             var rightPadding = _columns[cellIndex].Padding.Right; | ||||||
|  |                             if (rightPadding > 0) | ||||||
|  |                             { | ||||||
|  |                                 result.Add(new Segment(new string(' ', rightPadding))); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         if (lastCell && showBorder) | ||||||
|  |                         { | ||||||
|  |                             // Add right column edge | ||||||
|  |                             result.Add(new Segment(border.GetPart(BorderPart.CellRight))); | ||||||
|  |                         } | ||||||
|  |                         else if (showBorder) | ||||||
|  |                         { | ||||||
|  |                             // Add column separator | ||||||
|  |                             result.Add(new Segment(border.GetPart(BorderPart.CellSeparator))); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     result.Add(Segment.LineBreak); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Show header separator? | ||||||
|  |                 if (firstRow && showBorder && ShowHeaders && hasRows) | ||||||
|  |                 { | ||||||
|  |                     result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomLeft))); | ||||||
|  |                     foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate()) | ||||||
|  |                     { | ||||||
|  |                         var padding = _columns[columnIndex].Padding; | ||||||
|  |  | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Left))); // Left padding | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, columnWidth))); | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.HeaderBottom, padding.Right))); // Right padding | ||||||
|  |  | ||||||
|  |                         if (!lastColumn) | ||||||
|  |                         { | ||||||
|  |                             result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomSeparator))); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     result.Add(new Segment(border.GetPart(BorderPart.HeaderBottomRight))); | ||||||
|  |                     result.Add(Segment.LineBreak); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Show bottom of footer? | ||||||
|  |                 if (lastRow && showBorder) | ||||||
|  |                 { | ||||||
|  |                     result.Add(new Segment(border.GetPart(BorderPart.FooterBottomLeft))); | ||||||
|  |                     foreach (var (columnIndex, first, lastColumn, columnWidth) in columnWidths.Enumerate()) | ||||||
|  |                     { | ||||||
|  |                         var padding = _columns[columnIndex].Padding; | ||||||
|  |  | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Left))); // Left padding | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, columnWidth))); | ||||||
|  |                         result.Add(new Segment(border.GetPart(BorderPart.FooterBottom, padding.Right))); // Right padding | ||||||
|  |  | ||||||
|  |                         if (!lastColumn) | ||||||
|  |                         { | ||||||
|  |                             result.Add(new Segment(border.GetPart(BorderPart.FooterBottomSeparator))); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     result.Add(new Segment(border.GetPart(BorderPart.FooterBottomRight))); | ||||||
|  |                     result.Add(Segment.LineBreak); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool ShouldExpand() | ||||||
|  |         { | ||||||
|  |             return Expand || Width != null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								src/Spectre.Console/Rendering/TableColumn.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/Spectre.Console/Rendering/TableColumn.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | using System; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Represents a table column. | ||||||
|  |     /// </summary> | ||||||
|  |     public sealed class TableColumn : IAlignable | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets the text associated with the column. | ||||||
|  |         /// </summary> | ||||||
|  |         public IRenderable Text { get; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the width of the column. | ||||||
|  |         /// If <c>null</c>, the column will adapt to it's contents. | ||||||
|  |         /// </summary> | ||||||
|  |         public int? Width { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the padding of the column. | ||||||
|  |         /// </summary> | ||||||
|  |         public Padding Padding { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets a value indicating whether wrapping of | ||||||
|  |         /// text within the column should be prevented. | ||||||
|  |         /// </summary> | ||||||
|  |         public bool NoWrap { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the alignment of the column. | ||||||
|  |         /// </summary> | ||||||
|  |         public Justify? Alignment { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="TableColumn"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="text">The table column text.</param> | ||||||
|  |         public TableColumn(string text) | ||||||
|  |             : this(new Markup(text)) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="TableColumn"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="renderable">The <see cref="IRenderable"/> instance to use as the table column.</param> | ||||||
|  |         public TableColumn(IRenderable renderable) | ||||||
|  |         { | ||||||
|  |             Text = renderable ?? throw new ArgumentNullException(nameof(renderable)); | ||||||
|  |             Width = null; | ||||||
|  |             Padding = new Padding(1, 1); | ||||||
|  |             NoWrap = false; | ||||||
|  |             Alignment = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										78
									
								
								src/Spectre.Console/Rendering/TableExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/Spectre.Console/Rendering/TableExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | using System; | ||||||
|  | using System.Linq; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Contains extension methods for <see cref="Table"/>. | ||||||
|  |     /// </summary> | ||||||
|  |     public static class TableExtensions | ||||||
|  |     { | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a column to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="table">The table to add the column to.</param> | ||||||
|  |         /// <param name="column">The column to add.</param> | ||||||
|  |         /// <returns>The added <see cref="TableColumn"/> instance.</returns> | ||||||
|  |         public static TableColumn AddColumn(this Table table, string column) | ||||||
|  |         { | ||||||
|  |             if (table is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(table)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (column is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(column)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var tableColumn = new TableColumn(column); | ||||||
|  |             table.AddColumn(tableColumn); | ||||||
|  |  | ||||||
|  |             return tableColumn; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds multiple columns to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="table">The table to add the columns to.</param> | ||||||
|  |         /// <param name="columns">The columns to add.</param> | ||||||
|  |         public static void AddColumns(this Table table, params string[] columns) | ||||||
|  |         { | ||||||
|  |             if (table is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(table)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var column in columns) | ||||||
|  |             { | ||||||
|  |                 AddColumn(table, column); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Adds a row to the table. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="table">The table to add the row to.</param> | ||||||
|  |         /// <param name="columns">The row columns to add.</param> | ||||||
|  |         public static void AddRow(this Table table, params string[] columns) | ||||||
|  |         { | ||||||
|  |             if (table is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(table)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (columns is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(columns)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             table.AddRow(columns.Select(column => new Markup(column)).ToArray()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										272
									
								
								src/Spectre.Console/Rendering/Text.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								src/Spectre.Console/Rendering/Text.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,272 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Diagnostics; | ||||||
|  | using System.Diagnostics.CodeAnalysis; | ||||||
|  | using System.Linq; | ||||||
|  | using Spectre.Console.Internal; | ||||||
|  | using Spectre.Console.Rendering; | ||||||
|  |  | ||||||
|  | namespace Spectre.Console | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// A renderable piece of text. | ||||||
|  |     /// </summary> | ||||||
|  |     [DebuggerDisplay("{_text,nq}")] | ||||||
|  |     [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||||
|  |     public sealed class Text : Renderable, IAlignable | ||||||
|  |     { | ||||||
|  |         private readonly List<SegmentLine> _lines; | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets an empty <see cref="Text"/> instance. | ||||||
|  |         /// </summary> | ||||||
|  |         public static Text Empty { get; } = new Text(string.Empty); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Gets or sets the text alignment. | ||||||
|  |         /// </summary> | ||||||
|  |         public Justify? Alignment { get; set; } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Text"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         public Text() | ||||||
|  |         { | ||||||
|  |             _lines = new List<SegmentLine>(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Initializes a new instance of the <see cref="Text"/> class. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="text">The text.</param> | ||||||
|  |         /// <param name="style">The style of the text.</param> | ||||||
|  |         public Text(string text, Style? style = null) | ||||||
|  |             : this() | ||||||
|  |         { | ||||||
|  |             if (text is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(text)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Append(text, style); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Creates a <see cref="Text"/> instance representing | ||||||
|  |         /// the specified markup text. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="text">The markup text.</param> | ||||||
|  |         /// <param name="style">The text style.</param> | ||||||
|  |         /// <returns>a <see cref="Text"/> instance representing the specified markup text.</returns> | ||||||
|  |         public static Text Markup(string text, Style? style = null) | ||||||
|  |         { | ||||||
|  |             var result = MarkupParser.Parse(text, style ?? Style.Plain); | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override Measurement Measure(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             if (_lines.Count == 0) | ||||||
|  |             { | ||||||
|  |                 return new Measurement(0, 0); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding))); | ||||||
|  |             var max = _lines.Max(x => x.CellWidth(context.Encoding)); | ||||||
|  |  | ||||||
|  |             return new Measurement(min, max); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc/> | ||||||
|  |         protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             if (context is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(context)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (_lines.Count == 0) | ||||||
|  |             { | ||||||
|  |                 return Array.Empty<Segment>(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var lines = SplitLines(context, maxWidth); | ||||||
|  |  | ||||||
|  |             // Justify lines | ||||||
|  |             var justification = context.Justification ?? Alignment ?? Justify.Left; | ||||||
|  |             foreach (var (_, _, last, line) in lines.Enumerate()) | ||||||
|  |             { | ||||||
|  |                 var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding)); | ||||||
|  |                 if (length < maxWidth) | ||||||
|  |                 { | ||||||
|  |                     // Justify right side | ||||||
|  |                     if (justification == Justify.Right) | ||||||
|  |                     { | ||||||
|  |                         var diff = maxWidth - length; | ||||||
|  |                         line.Prepend(new Segment(new string(' ', diff))); | ||||||
|  |                     } | ||||||
|  |                     else if (justification == Justify.Center) | ||||||
|  |                     { | ||||||
|  |                         // Left side. | ||||||
|  |                         var diff = (maxWidth - length) / 2; | ||||||
|  |                         line.Prepend(new Segment(new string(' ', diff))); | ||||||
|  |  | ||||||
|  |                         // Right side | ||||||
|  |                         line.Add(new Segment(new string(' ', diff))); | ||||||
|  |                         var remainder = (maxWidth - length) % 2; | ||||||
|  |                         if (remainder != 0) | ||||||
|  |                         { | ||||||
|  |                             line.Add(new Segment(new string(' ', remainder))); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return new SegmentLineEnumerator(lines); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Appends a piece of text. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="text">The text to append.</param> | ||||||
|  |         /// <param name="style">The style of the appended text.</param> | ||||||
|  |         public void Append(string text, Style? style = null) | ||||||
|  |         { | ||||||
|  |             if (text is null) | ||||||
|  |             { | ||||||
|  |                 throw new ArgumentNullException(nameof(text)); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             foreach (var (_, first, last, part) in text.SplitLines().Enumerate()) | ||||||
|  |             { | ||||||
|  |                 var current = part; | ||||||
|  |  | ||||||
|  |                 if (first) | ||||||
|  |                 { | ||||||
|  |                     var line = _lines.LastOrDefault(); | ||||||
|  |                     if (line == null) | ||||||
|  |                     { | ||||||
|  |                         _lines.Add(new SegmentLine()); | ||||||
|  |                         line = _lines.Last(); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (string.IsNullOrEmpty(current)) | ||||||
|  |                     { | ||||||
|  |                         line.Add(Segment.Empty); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         foreach (var span in current.SplitWords()) | ||||||
|  |                         { | ||||||
|  |                             line.Add(new Segment(span, style ?? Style.Plain)); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     var line = new SegmentLine(); | ||||||
|  |  | ||||||
|  |                     if (string.IsNullOrEmpty(current)) | ||||||
|  |                     { | ||||||
|  |                         line.Add(Segment.Empty); | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         foreach (var span in current.SplitWords()) | ||||||
|  |                         { | ||||||
|  |                             line.Add(new Segment(span, style ?? Style.Plain)); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     _lines.Add(line); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private List<SegmentLine> Clone() | ||||||
|  |         { | ||||||
|  |             var result = new List<SegmentLine>(); | ||||||
|  |  | ||||||
|  |             foreach (var line in _lines) | ||||||
|  |             { | ||||||
|  |                 var newLine = new SegmentLine(); | ||||||
|  |                 foreach (var segment in line) | ||||||
|  |                 { | ||||||
|  |                     newLine.Add(segment); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 result.Add(newLine); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private List<SegmentLine> SplitLines(RenderContext context, int maxWidth) | ||||||
|  |         { | ||||||
|  |             if (_lines.Max(x => x.CellWidth(context.Encoding)) <= maxWidth) | ||||||
|  |             { | ||||||
|  |                 return Clone(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var lines = new List<SegmentLine>(); | ||||||
|  |             var line = new SegmentLine(); | ||||||
|  |  | ||||||
|  |             var newLine = true; | ||||||
|  |             using (var iterator = new SegmentLineIterator(_lines)) | ||||||
|  |             { | ||||||
|  |                 while (iterator.MoveNext()) | ||||||
|  |                 { | ||||||
|  |                     var current = iterator.Current; | ||||||
|  |                     if (current == null) | ||||||
|  |                     { | ||||||
|  |                         throw new InvalidOperationException("Iterator returned empty segment."); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (newLine && current.IsWhiteSpace && !current.IsLineBreak) | ||||||
|  |                     { | ||||||
|  |                         newLine = false; | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     newLine = false; | ||||||
|  |  | ||||||
|  |                     if (current.IsLineBreak) | ||||||
|  |                     { | ||||||
|  |                         line.Add(current); | ||||||
|  |                         lines.Add(line); | ||||||
|  |                         line = new SegmentLine(); | ||||||
|  |                         newLine = true; | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var length = current.CellLength(context.Encoding); | ||||||
|  |                     if (line.CellWidth(context.Encoding) + length > maxWidth) | ||||||
|  |                     { | ||||||
|  |                         line.Add(Segment.Empty); | ||||||
|  |                         lines.Add(line); | ||||||
|  |                         line = new SegmentLine(); | ||||||
|  |                         newLine = true; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (newLine && current.IsWhiteSpace) | ||||||
|  |                     { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     newLine = false; | ||||||
|  |  | ||||||
|  |                     line.Add(current); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Flush remaining. | ||||||
|  |             if (line.Count > 0) | ||||||
|  |             { | ||||||
|  |                 lines.Add(line); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return lines; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,30 +2,29 @@ | |||||||
|  |  | ||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <TargetFramework>netstandard2.0</TargetFramework> |     <TargetFramework>netstandard2.0</TargetFramework> | ||||||
|  |     <Nullable>enable</Nullable> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" /> |     <AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" /> | ||||||
|   </ItemGroup> |     <None Include="../../gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" /> | ||||||
|  |  | ||||||
|   <ItemGroup> |  | ||||||
|     <Compile Update="**/ColorPalette.*.cs"> |  | ||||||
|       <DependentUpon>**/ColorPalette.cs</DependentUpon> |  | ||||||
|     </Compile> |  | ||||||
|     <Compile Update="Color.*.cs"> |  | ||||||
|       <DependentUpon>Color.cs</DependentUpon> |  | ||||||
|     </Compile> |  | ||||||
|     <Compile Update="AnsiConsole.*.cs"> |  | ||||||
|       <DependentUpon>AnsiConsole.cs</DependentUpon> |  | ||||||
|     </Compile> |  | ||||||
|     <Compile Update="ConsoleExtensions.*.cs"> |  | ||||||
|       <DependentUpon>ConsoleExtensions.cs</DependentUpon> |  | ||||||
|     </Compile> |  | ||||||
|  |  | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="System.Memory" Version="4.5.4" /> |     <PackageReference Include="System.Memory" Version="4.5.4" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|  |   <ItemGroup> | ||||||
|  |     <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" /> | ||||||
|  |     <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" /> | ||||||
|  |     <PackageReference Include="Nullable" Version="1.2.1"> | ||||||
|  |       <PrivateAssets>all</PrivateAssets> | ||||||
|  |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|  |     </PackageReference> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <AnnotatedReferenceAssemblyVersion>3.0.0</AnnotatedReferenceAssemblyVersion> | ||||||
|  |     <GenerateNullableAttributes>False</GenerateNullableAttributes> | ||||||
|  |   </PropertyGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -57,6 +57,15 @@ namespace Spectre.Console | |||||||
|             return StyleParser.Parse(text); |             return StyleParser.Parse(text); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Creates a copy of the current <see cref="Style"/>. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns>A copy of the current <see cref="Style"/>.</returns> | ||||||
|  |         public Style Clone() | ||||||
|  |         { | ||||||
|  |             return new Style(Foreground, Background, Decoration); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         /// <summary> |         /// <summary> | ||||||
|         /// Converts the string representation of a style to its <see cref="Style"/> equivalent. |         /// Converts the string representation of a style to its <see cref="Style"/> equivalent. | ||||||
|         /// A return value indicates whether the operation succeeded. |         /// A return value indicates whether the operation succeeded. | ||||||
| @@ -67,7 +76,7 @@ namespace Spectre.Console | |||||||
|         /// if the conversion succeeded, or <c>null</c> if the conversion failed. |         /// if the conversion succeeded, or <c>null</c> if the conversion failed. | ||||||
|         /// </param> |         /// </param> | ||||||
|         /// <returns><c>true</c> if s was converted successfully; otherwise, <c>false</c>.</returns> |         /// <returns><c>true</c> if s was converted successfully; otherwise, <c>false</c>.</returns> | ||||||
|         public static bool TryParse(string text, out Style result) |         public static bool TryParse(string text, out Style? result) | ||||||
|         { |         { | ||||||
|             return StyleParser.TryParse(text, out result); |             return StyleParser.TryParse(text, out result); | ||||||
|         } |         } | ||||||
| @@ -113,13 +122,13 @@ namespace Spectre.Console | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |         /// <inheritdoc/> | ||||||
|         public override bool Equals(object obj) |         public override bool Equals(object? obj) | ||||||
|         { |         { | ||||||
|             return Equals(obj as Style); |             return Equals(obj as Style); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /// <inheritdoc/> |         /// <inheritdoc/> | ||||||
|         public bool Equals(Style other) |         public bool Equals(Style? other) | ||||||
|         { |         { | ||||||
|             if (other == null) |             if (other == null) | ||||||
|             { |             { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user