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 | |
|---|---|---|---|
|  | d7bbaf4a85 | ||
|  | 0119364728 | ||
|  | 1d74fb909c | ||
|  | 5d132220ba | ||
|  | a273f74758 | ||
|  | 717931f11c | ||
|  | bcfc495843 | ||
|  | 9aa36c4cf0 | ||
|  | 22d4af4482 | ||
|  | f4d1796e40 | ||
|  | 2dd0eb9f74 | ||
|  | fa85216554 | ||
|  | d475e3b30a | ||
|  | 9637066927 | ||
|  | 0b4321115a | ||
|  | 5cd9ece31a | ||
|  | b0341862cf | ||
|  | 2e7b3d520a | ||
|  | 646f51a628 | ||
|  | a0bd481255 | ||
|  | 6d197c5140 | ||
|  | 108e56c229 | ||
|  | 66994cd904 | ||
|  | f9bd936254 | ||
|  | a068fc68c3 | ||
|  | aa34c145b9 | 
							
								
								
									
										6
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -2,9 +2,7 @@ name: Continuous Integration | ||||
| on: pull_request | ||||
|  | ||||
| env: | ||||
|   # Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages | ||||
|   DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | ||||
|   # Disable sending usage data to Microsoft | ||||
|   DOTNET_CLI_TELEMETRY_OPTOUT: true | ||||
|  | ||||
| jobs: | ||||
| @@ -28,10 +26,6 @@ jobs: | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: 'Get Git tags' | ||||
|         run: git fetch --tags | ||||
|         shell: bash | ||||
|  | ||||
|       - name: Setup dotnet | ||||
|         uses: actions/setup-dotnet@v1 | ||||
|         with: | ||||
|   | ||||
							
								
								
									
										10
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -8,9 +8,7 @@ on: | ||||
|       - main | ||||
|  | ||||
| env: | ||||
|   # Set the DOTNET_SKIP_FIRST_TIME_EXPERIENCE environment variable to stop wasting time caching packages | ||||
|   DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true | ||||
|   # Disable sending usage data to Microsoft | ||||
|   DOTNET_CLI_TELEMETRY_OPTOUT: true | ||||
|  | ||||
| jobs: | ||||
| @@ -39,10 +37,6 @@ jobs: | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: 'Get Git tags' | ||||
|         run: git fetch --tags | ||||
|         shell: bash | ||||
|  | ||||
|       - name: Setup dotnet | ||||
|         uses: actions/setup-dotnet@v1 | ||||
|         with: | ||||
| @@ -69,10 +63,6 @@ jobs: | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: 'Get Git tags' | ||||
|         run: git fetch --tags | ||||
|         shell: bash | ||||
|  | ||||
|       - name: Setup dotnet | ||||
|         uses: actions/setup-dotnet@v1 | ||||
|         with: | ||||
|   | ||||
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| # `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.   | ||||
| It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)  | ||||
| @@ -13,8 +13,9 @@ for Python. | ||||
| 3. [Usage](#usage)   | ||||
|    3.1. [Using the static API](#using-the-static-api)   | ||||
|    3.2. [Creating a console](#creating-a-console) | ||||
| 4. [Available styles](#available-styles) | ||||
| 5. [Predefined colors](#predefined-colors) | ||||
| 4. [Running examples](#running-examples) | ||||
| 5. [Available styles](#available-styles) | ||||
| 6. [Predefined colors](#predefined-colors) | ||||
|  | ||||
| ## 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  | ||||
| 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 | ||||
|  | ||||
| _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> | ||||
							
								
								
									
										26
									
								
								examples/Grid/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								examples/Grid/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| using System; | ||||
| 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> | ||||
|     <OutputType>Exe</OutputType> | ||||
|     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
|     <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild> | ||||
|     <IsPackable>false</IsPackable> | ||||
|     <Description>Demonstrates how to render items in panels.</Description> | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> | ||||
|     <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
							
								
								
									
										50
									
								
								examples/Panel/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								examples/Panel/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| using System; | ||||
| 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, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								examples/Table/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								examples/Table/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| using System; | ||||
| using Spectre.Console; | ||||
|  | ||||
| namespace TableExample | ||||
| { | ||||
|     class Program | ||||
|     { | ||||
|         static void Main(string[] args) | ||||
|         { | ||||
|             // A simple table§ | ||||
|             RenderSimpleTable(); | ||||
|  | ||||
|             // A big table | ||||
|             RenderBigTable(); | ||||
|         } | ||||
|  | ||||
|         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,7 @@ dotnet_diagnostic.CA1032.severity = none | ||||
| dotnet_diagnostic.CA1826.severity = none | ||||
|  | ||||
| # 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 | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
|     <Authors>Patrik Svensson</Authors> | ||||
|     <RepositoryType>git</RepositoryType> | ||||
|     <RepositoryUrl>https://github.com/spectresystems/spectre.console</RepositoryUrl> | ||||
|     <PackageIcon>small-logo.png</PackageIcon> | ||||
|     <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> | ||||
|     <PackageProjectUrl>https://github.com/spectresystems/spectre.console</PackageProjectUrl> | ||||
|     <PackageLicenseExpression>MIT</PackageLicenseExpression> | ||||
| @@ -33,7 +34,7 @@ | ||||
|   <ItemGroup Label="Package References"> | ||||
|     <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.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> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </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,83 +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)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -7,7 +7,7 @@ namespace Spectre.Console.Tests | ||||
| { | ||||
|     public sealed class PlainConsole : IAnsiConsole, IDisposable | ||||
|     { | ||||
|         public Capabilities Capabilities => throw new NotSupportedException(); | ||||
|         public Capabilities Capabilities { get; } | ||||
|         public Encoding Encoding { get; } | ||||
|  | ||||
|         public int Width { get; } | ||||
| @@ -18,14 +18,19 @@ namespace Spectre.Console.Tests | ||||
|         public Color Background { get; set; } | ||||
|  | ||||
|         public StringWriter Writer { get; } | ||||
|         public string RawOutput => Writer.ToString(); | ||||
|         public string Output => Writer.ToString().TrimEnd('\n'); | ||||
|         public IReadOnlyList<string> Lines => Output.Split(new char[] { '\n' }); | ||||
|  | ||||
|         public PlainConsole(int width = 80, int height = 9000, Encoding encoding = null) | ||||
|         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; | ||||
|             Height = height; | ||||
|             Encoding = encoding ?? Encoding.UTF8; | ||||
|             Writer = new StringWriter(); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -2,16 +2,17 @@ | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||
|  | ||||
|     <IsPackable>false</IsPackable> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <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="xunit" Version="2.4.0" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> | ||||
|     <PackageReference Include="coverlet.collector" Version="1.2.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.1" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -12,7 +12,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|         { | ||||
|             [Theory] | ||||
|             [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) | ||||
|             { | ||||
|                 // Given | ||||
| @@ -26,7 +26,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|             } | ||||
|  | ||||
|             [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) | ||||
|             { | ||||
|                 // Given | ||||
|   | ||||
| @@ -246,6 +246,38 @@ namespace Spectre.Console.Tests.Unit | ||||
|  | ||||
|         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] | ||||
|             [InlineData(AnsiSupport.Yes)] | ||||
|             [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.Composition; | ||||
| 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 | ||||
|                 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 | ||||
|   | ||||
| @@ -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"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										210
									
								
								src/Spectre.Console.Tests/Unit/GridTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/Spectre.Console.Tests/Unit/GridTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | ||||
| 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_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); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(Text.New("Hello World"))); | ||||
|             console.Render(new Panel(new Text("Hello World"))); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
| @@ -21,6 +21,25 @@ namespace Spectre.Console.Tests.Unit | ||||
|             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] | ||||
|         public void Should_Render_Panel_With_Unicode_Correctly() | ||||
|         { | ||||
| @@ -28,7 +47,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|             var console = new PlainConsole(width: 80); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(Text.New(" \n💩\n "))); | ||||
|             console.Render(new Panel(new Text(" \n💩\n "))); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(5); | ||||
| @@ -46,7 +65,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|             var console = new PlainConsole(width: 80); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(Text.New("Hello World\nFoo Bar"))); | ||||
|             console.Render(new Panel(new Text("Hello World\nFoo Bar"))); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(4); | ||||
| @@ -62,8 +81,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 80); | ||||
|             var text = new Panel( | ||||
|                 Text.New("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦"), | ||||
|                 content: Justify.Center); | ||||
|                 Text.Markup("I heard [underline on blue]you[/] like 📦\n\n\n\nSo I put a 📦 in a 📦")); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(text); | ||||
| @@ -71,7 +89,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(7); | ||||
|             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[3].ShouldBe("│                       │"); | ||||
|             console.Lines[4].ShouldBe("│                       │"); | ||||
| @@ -80,19 +98,23 @@ namespace Spectre.Console.Tests.Unit | ||||
|         } | ||||
| 
 | ||||
|         [Fact] | ||||
|         public void Should_Fit_Panel_To_Parent_If_Enabled() | ||||
|         public void Should_Expand_Panel_If_Enabled() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(width: 25); | ||||
|             var console = new PlainConsole(width: 80); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(Text.New("Hello World"), fit: true)); | ||||
|             console.Render(new Panel(new Text("Hello World")) | ||||
|             { | ||||
|                 Expand = true, | ||||
|             }); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
|             console.Lines[0].ShouldBe("┌───────────────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│ Hello World           │"); | ||||
|             console.Lines[2].ShouldBe("└───────────────────────┘"); | ||||
|             console.Lines[0].Length.ShouldBe(80); | ||||
|             console.Lines[0].ShouldBe("┌──────────────────────────────────────────────────────────────────────────────┐"); | ||||
|             console.Lines[1].ShouldBe("│ Hello World                                                                  │"); | ||||
|             console.Lines[2].ShouldBe("└──────────────────────────────────────────────────────────────────────────────┘"); | ||||
|         } | ||||
| 
 | ||||
|         [Fact] | ||||
| @@ -102,7 +124,12 @@ namespace Spectre.Console.Tests.Unit | ||||
|             var console = new PlainConsole(width: 25); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Right)); | ||||
|             console.Render( | ||||
|                 new Panel( | ||||
|                     new Text("Hello World").WithAlignment(Justify.Right)) | ||||
|                 { | ||||
|                     Expand = true, | ||||
|                 }); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
| @@ -118,7 +145,12 @@ namespace Spectre.Console.Tests.Unit | ||||
|             var console = new PlainConsole(width: 25); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(Text.New("Hello World"), fit: true, content: Justify.Center)); | ||||
|             console.Render( | ||||
|                 new Panel( | ||||
|                     new Text("Hello World").WithAlignment(Justify.Center)) | ||||
|                 { | ||||
|                     Expand = true, | ||||
|                 }); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(3); | ||||
| @@ -134,7 +166,7 @@ namespace Spectre.Console.Tests.Unit | ||||
|             var console = new PlainConsole(width: 80); | ||||
| 
 | ||||
|             // When | ||||
|             console.Render(new Panel(new Panel(Text.New("Hello World")))); | ||||
|             console.Render(new Panel(new Panel(new Text("Hello World")))); | ||||
| 
 | ||||
|             // Then | ||||
|             console.Lines.Count.ShouldBe(5); | ||||
| @@ -39,14 +39,18 @@ namespace Spectre.Console.Tests.Unit | ||||
|  | ||||
|             [Theory] | ||||
|             [InlineData("bold", Decoration.Bold)] | ||||
|             [InlineData("b", Decoration.Bold)] | ||||
|             [InlineData("dim", Decoration.Dim)] | ||||
|             [InlineData("i", Decoration.Italic)] | ||||
|             [InlineData("italic", Decoration.Italic)] | ||||
|             [InlineData("underline", Decoration.Underline)] | ||||
|             [InlineData("u", Decoration.Underline)] | ||||
|             [InlineData("invert", Decoration.Invert)] | ||||
|             [InlineData("conceal", Decoration.Conceal)] | ||||
|             [InlineData("slowblink", Decoration.SlowBlink)] | ||||
|             [InlineData("rapidblink", Decoration.RapidBlink)] | ||||
|             [InlineData("strikethrough", Decoration.Strikethrough)] | ||||
|             [InlineData("s", Decoration.Strikethrough)] | ||||
|             public void Should_Parse_Decoration(string text, Decoration decoration) | ||||
|             { | ||||
|                 // Given, When | ||||
| @@ -126,108 +130,83 @@ namespace Spectre.Console.Tests.Unit | ||||
|                 result.ShouldBeOfType<InvalidOperationException>(); | ||||
|                 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 | ||||
|         { | ||||
|             [Fact] | ||||
|             public void Default_Keyword_Should_Return_Default_Style() | ||||
|             public void Should_Return_True_If_Parsing_Succeeded() | ||||
|             { | ||||
|                 // Given, When | ||||
|                 var result = Style.TryParse("default", out var style); | ||||
|                 var result = Style.TryParse("bold", out var style); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeTrue(); | ||||
|                 style.ShouldNotBeNull(); | ||||
|                 style.Foreground.ShouldBe(Color.Default); | ||||
|                 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); | ||||
|                 style.Decoration.ShouldBe(Decoration.Bold); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Parse_Text_And_Decoration() | ||||
|             public void Should_Return_False_If_Parsing_Failed() | ||||
|             { | ||||
|                 // Given, When | ||||
|                 var result = Style.TryParse("bold underline blue on green", out var style); | ||||
|  | ||||
|                 // 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 var style); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeFalse(); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Throw_If_Background_Is_Set_Twice() | ||||
|             { | ||||
|                 // Given, When | ||||
|                 var result = Style.TryParse("green on blue yellow", out var style); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeFalse(); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Throw_If_Color_Name_Could_Not_Be_Found() | ||||
|             { | ||||
|                 // Given, When | ||||
|                 var result = Style.TryParse("bold lol", out var style); | ||||
|  | ||||
|                 // 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 var style); | ||||
|                 var result = Style.TryParse("lol", out _); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeFalse(); | ||||
|   | ||||
							
								
								
									
										374
									
								
								src/Spectre.Console.Tests/Unit/TableTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								src/Spectre.Console.Tests/Unit/TableTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,374 @@ | ||||
| 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_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("└─────┴─────┴────────┘"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										85
									
								
								src/Spectre.Console.Tests/Unit/TextTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/Spectre.Console.Tests/Unit/TextTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| using System.Text; | ||||
| using Shouldly; | ||||
| using Spectre.Console.Composition; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit | ||||
| { | ||||
|     public sealed class TextTests | ||||
|     { | ||||
|         public sealed class Measuring | ||||
|         { | ||||
|             [Fact] | ||||
|             public void Should_Return_The_Longest_Word_As_Minimum_Width() | ||||
|             { | ||||
|                 var text = new Text("Foo Bar Baz\nQux\nLol mobile"); | ||||
|  | ||||
|                 var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||
|  | ||||
|                 result.Min.ShouldBe(6); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Return_The_Longest_Line_As_Maximum_Width() | ||||
|             { | ||||
|                 var text = new Text("Foo Bar Baz\nQux\nLol mobile"); | ||||
|  | ||||
|                 var result = text.Measure(new RenderContext(Encoding.Unicode, false), 80); | ||||
|  | ||||
|                 result.Max.ShouldBe(11); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public sealed class Rendering | ||||
|         { | ||||
|             [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"); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Write_Line_Breaks() | ||||
|             { | ||||
|                 // Given | ||||
|                 var fixture = new PlainConsole(width: 5); | ||||
|                 var text = new Text("Hello\n\nWorld"); | ||||
|  | ||||
|                 // When | ||||
|                 fixture.Render(text); | ||||
|  | ||||
|                 // Then | ||||
|                 fixture.RawOutput.ShouldBe("Hello\n\nWorld"); | ||||
|             } | ||||
|  | ||||
|             [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 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{9F1AC4C1-766E-4421-8A78-B28F5BCDD94F}" | ||||
| 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}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		.editorconfig = .editorconfig | ||||
| @@ -17,6 +15,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | ||||
| 		stylecop.json = stylecop.json | ||||
| 	EndProjectSection | ||||
| 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 | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		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|x86.ActiveCfg = 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 | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{272E6092-BD31-4EB6-A9FF-F4179F91958F}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{94ECCBA8-7EBF-4B53-8379-52EB2327417E}.Release|x86.ActiveCfg = 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 | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	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 | ||||
| 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | ||||
| 	EndGlobalSection | ||||
|   | ||||
| @@ -10,12 +10,14 @@ namespace Spectre.Console | ||||
|     { | ||||
|         private static readonly Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(() => | ||||
|         { | ||||
|             return Create(new AnsiConsoleSettings | ||||
|             var console = Create(new AnsiConsoleSettings | ||||
|             { | ||||
|                 Ansi = AnsiSupport.Detect, | ||||
|                 ColorSystem = ColorSystemSupport.Detect, | ||||
|                 Out = System.Console.Out, | ||||
|             }); | ||||
|             Created = true; | ||||
|             return console; | ||||
|         }); | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -28,6 +30,8 @@ namespace Spectre.Console | ||||
|         /// </summary> | ||||
|         public static Capabilities Capabilities => Console.Capabilities; | ||||
|  | ||||
|         internal static bool Created { get; private set; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the buffer width of the console. | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -21,6 +21,6 @@ namespace Spectre.Console | ||||
|         /// <summary> | ||||
|         /// Gets or sets the out buffer. | ||||
|         /// </summary> | ||||
|         public TextWriter Out { get; set; } | ||||
|         public TextWriter? Out { get; set; } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -16,16 +16,33 @@ namespace Spectre.Console | ||||
|         /// </summary> | ||||
|         public ColorSystem ColorSystem { get; } | ||||
|  | ||||
|         internal Capabilities(bool supportsAnsi, ColorSystem colorSystem) | ||||
|         /// <summary> | ||||
|         /// Gets a value indicating whether or not | ||||
|         /// this is a legacy console (cmd.exe). | ||||
|         /// </summary> | ||||
|         /// <remarks> | ||||
|         /// Only relevant when running on Microsoft Windows. | ||||
|         /// </remarks> | ||||
|         public bool LegacyConsole { get; } | ||||
|  | ||||
|         /// <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; | ||||
|             ColorSystem = colorSystem; | ||||
|             LegacyConsole = legacyConsole; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override string ToString() | ||||
|         { | ||||
|             var supportsAnsi = SupportsAnsi ? "Yes" : "No"; | ||||
|             var legacyConsole = LegacyConsole ? "Legacy" : "Modern"; | ||||
|             var bits = ColorSystem switch | ||||
|             { | ||||
|                 ColorSystem.NoColors => "1 bit", | ||||
| @@ -33,10 +50,10 @@ namespace Spectre.Console | ||||
|                 ColorSystem.Standard => "4 bits", | ||||
|                 ColorSystem.EightBit => "8 bits", | ||||
|                 ColorSystem.TrueColor => "24 bits", | ||||
|                 _ => "?" | ||||
|                 _ => "?", | ||||
|             }; | ||||
|  | ||||
|             return $"ANSI={supportsAnsi}, Colors={ColorSystem} ({bits})"; | ||||
|             return $"ANSI={supportsAnsi}, Colors={ColorSystem}, Kind={legacyConsole} ({bits})"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -74,7 +74,7 @@ namespace Spectre.Console | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override bool Equals(object obj) | ||||
|         public override bool Equals(object? obj) | ||||
|         { | ||||
|             return obj is Color color && Equals(color); | ||||
|         } | ||||
| @@ -82,7 +82,8 @@ namespace Spectre.Console | ||||
|         /// <inheritdoc/> | ||||
|         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> | ||||
|   | ||||
							
								
								
									
										108
									
								
								src/Spectre.Console/Composition/Border.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/Spectre.Console/Composition/Border.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Globalization; | ||||
| using System.Linq; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a border used by tables. | ||||
|     /// </summary> | ||||
|     public abstract class Border | ||||
|     { | ||||
|         private readonly Dictionary<BorderPart, string> _lookup; | ||||
|  | ||||
|         private static readonly Dictionary<BorderKind, Border> _borders = new Dictionary<BorderKind, Border> | ||||
|         { | ||||
|             { BorderKind.None, new NoBorder() }, | ||||
|             { BorderKind.Ascii, new AsciiBorder() }, | ||||
|             { BorderKind.Square, new SquareBorder() }, | ||||
|             { BorderKind.Rounded, new RoundedBorder() }, | ||||
|         }; | ||||
|  | ||||
|         private static readonly Dictionary<BorderKind, BorderKind> _safeLookup = new Dictionary<BorderKind, BorderKind> | ||||
|         { | ||||
|             { BorderKind.Rounded, BorderKind.Square }, | ||||
|         }; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Border"/> class. | ||||
|         /// </summary> | ||||
|         protected Border() | ||||
|         { | ||||
|             _lookup = Initialize(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets a <see cref="Border"/> represented by the specified <see cref="BorderKind"/>. | ||||
|         /// </summary> | ||||
|         /// <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> | ||||
|         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)) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Unknown border kind"); | ||||
|             } | ||||
|  | ||||
|             return border; | ||||
|         } | ||||
|  | ||||
|         private Dictionary<BorderPart, string> Initialize() | ||||
|         { | ||||
|             var lookup = new Dictionary<BorderPart, string>(); | ||||
|             foreach (BorderPart? part in Enum.GetValues(typeof(BorderPart))) | ||||
|             { | ||||
|                 if (part == null) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var text = GetBoxPart(part.Value); | ||||
|                 if (text.Length > 1) | ||||
|                 { | ||||
|                     throw new InvalidOperationException("A box part cannot contain more than one character."); | ||||
|                 } | ||||
|  | ||||
|                 lookup.Add(part.Value, GetBoxPart(part.Value)); | ||||
|             } | ||||
|  | ||||
|             return lookup; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the string representation of a specific border part. | ||||
|         /// </summary> | ||||
|         /// <param name="part">The part to get a string representation for.</param> | ||||
|         /// <param name="count">The number of repetitions.</param> | ||||
|         /// <returns>A string representation of the specified border part.</returns> | ||||
|         public string GetPart(BorderPart part, int count) | ||||
|         { | ||||
|             // TODO: This need some optimization... | ||||
|             return string.Join(string.Empty, Enumerable.Repeat(GetBoxPart(part)[0], count)); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the string representation of a specific border part. | ||||
|         /// </summary> | ||||
|         /// <param name="part">The part to get a string representation for.</param> | ||||
|         /// <returns>A string representation of the specified border part.</returns> | ||||
|         public string GetPart(BorderPart part) | ||||
|         { | ||||
|             return _lookup[part].ToString(CultureInfo.InvariantCulture); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the character representing the specified border part. | ||||
|         /// </summary> | ||||
|         /// <param name="part">The part to get the character representation for.</param> | ||||
|         /// <returns>A character representation of the specified border part.</returns> | ||||
|         protected abstract string GetBoxPart(BorderPart part); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/Spectre.Console/Composition/BorderKind.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/Spectre.Console/Composition/BorderKind.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents different kinds of borders. | ||||
|     /// </summary> | ||||
|     public enum BorderKind | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// No border. | ||||
|         /// </summary> | ||||
|         None = 0, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A square border. | ||||
|         /// </summary> | ||||
|         Square = 1, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// An old school ASCII border. | ||||
|         /// </summary> | ||||
|         Ascii = 2, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A rounded border. | ||||
|         /// </summary> | ||||
|         Rounded = 3, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										98
									
								
								src/Spectre.Console/Composition/BorderPart.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/Spectre.Console/Composition/BorderPart.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents the different border parts. | ||||
|     /// </summary> | ||||
|     public enum BorderPart | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// The top left part of a header. | ||||
|         /// </summary> | ||||
|         HeaderTopLeft, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The top part of a header. | ||||
|         /// </summary> | ||||
|         HeaderTop, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The top separator part of a header. | ||||
|         /// </summary> | ||||
|         HeaderTopSeparator, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The top right part of a header. | ||||
|         /// </summary> | ||||
|         HeaderTopRight, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The left part of a header. | ||||
|         /// </summary> | ||||
|         HeaderLeft, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A header separator. | ||||
|         /// </summary> | ||||
|         HeaderSeparator, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The right part of a header. | ||||
|         /// </summary> | ||||
|         HeaderRight, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom left part of a header. | ||||
|         /// </summary> | ||||
|         HeaderBottomLeft, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom part of a header. | ||||
|         /// </summary> | ||||
|         HeaderBottom, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom separator part of a header. | ||||
|         /// </summary> | ||||
|         HeaderBottomSeparator, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom right part of a header. | ||||
|         /// </summary> | ||||
|         HeaderBottomRight, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The left part of a cell. | ||||
|         /// </summary> | ||||
|         CellLeft, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// A cell separator. | ||||
|         /// </summary> | ||||
|         CellSeparator, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The right part of a cell. | ||||
|         /// </summary> | ||||
|         CellRight, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom left part of a footer. | ||||
|         /// </summary> | ||||
|         FooterBottomLeft, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom part of a footer. | ||||
|         /// </summary> | ||||
|         FooterBottom, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom separator part of a footer. | ||||
|         /// </summary> | ||||
|         FooterBottomSeparator, | ||||
|  | ||||
|         /// <summary> | ||||
|         /// The bottom right part of a footer. | ||||
|         /// </summary> | ||||
|         FooterBottomRight, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/Spectre.Console/Composition/Borders/AsciiBorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Spectre.Console/Composition/Borders/AsciiBorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents an old school ASCII border. | ||||
|     /// </summary> | ||||
|     public sealed class AsciiBorder : 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."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/Spectre.Console/Composition/Borders/NoBorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Spectre.Console/Composition/Borders/NoBorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents an invisible border. | ||||
|     /// </summary> | ||||
|     public sealed class NoBorder : Border | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         protected override string GetBoxPart(BorderPart part) | ||||
|         { | ||||
|             return " "; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/Spectre.Console/Composition/Borders/RoundedBorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Spectre.Console/Composition/Borders/RoundedBorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <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."), | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										37
									
								
								src/Spectre.Console/Composition/Borders/SquareBorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Spectre.Console/Composition/Borders/SquareBorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a square border. | ||||
|     /// </summary> | ||||
|     public sealed class SquareBorder : 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,5 +1,4 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
| @@ -11,17 +10,17 @@ namespace Spectre.Console.Composition | ||||
|         /// <summary> | ||||
|         /// Measures the renderable object. | ||||
|         /// </summary> | ||||
|         /// <param name="encoding">The encoding to use.</param> | ||||
|         /// <param name="context">The render context.</param> | ||||
|         /// <param name="maxWidth">The maximum allowed width.</param> | ||||
|         /// <returns>The width of the object.</returns> | ||||
|         int Measure(Encoding encoding, int maxWidth); | ||||
|         /// <returns>The minimum and maximum width of the object.</returns> | ||||
|         Measurement Measure(RenderContext context, int maxWidth); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Renders the object. | ||||
|         /// </summary> | ||||
|         /// <param name="encoding">The encoding to use.</param> | ||||
|         /// <param name="width">The width of the render area.</param> | ||||
|         /// <param name="context">The render context.</param> | ||||
|         /// <param name="maxWidth">The maximum allowed width.</param> | ||||
|         /// <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, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Centered | ||||
|         /// Centered. | ||||
|         /// </summary> | ||||
|         Center = 2, | ||||
|     } | ||||
							
								
								
									
										77
									
								
								src/Spectre.Console/Composition/Measurement.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/Spectre.Console/Composition/Measurement.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <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/Composition/Padding.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/Spectre.Console/Composition/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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/Spectre.Console/Composition/RenderContext.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/Spectre.Console/Composition/RenderContext.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -24,11 +24,28 @@ namespace Spectre.Console.Composition | ||||
|         /// </summary> | ||||
|         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> | ||||
|         /// Gets the segment style. | ||||
|         /// </summary> | ||||
|         public Style Style { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets a segment representing a line break. | ||||
|         /// </summary> | ||||
|         public static Segment LineBreak { get; } = new Segment("\n", Style.Plain, true); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets an empty segment. | ||||
|         /// </summary> | ||||
|         public static Segment Empty { get; } = new Segment(string.Empty, Style.Plain); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Segment"/> class. | ||||
|         /// </summary> | ||||
| @@ -50,18 +67,15 @@ namespace Spectre.Console.Composition | ||||
|  | ||||
|         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; | ||||
|             IsLineBreak = lineBreak; | ||||
|         } | ||||
|  | ||||
|         /// <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); | ||||
|             IsWhiteSpace = string.IsNullOrWhiteSpace(text); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
| @@ -89,7 +103,7 @@ namespace Spectre.Console.Composition | ||||
|         /// </summary> | ||||
|         /// <param name="offset">The offset where to split the segment.</param> | ||||
|         /// <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) | ||||
|             { | ||||
| @@ -138,9 +152,9 @@ namespace Spectre.Console.Composition | ||||
|             { | ||||
|                 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 (first, second) = segment.Split(offset); | ||||
| @@ -161,7 +175,7 @@ namespace Spectre.Console.Composition | ||||
|                 { | ||||
|                     if (segment.Text == "\n") | ||||
|                     { | ||||
|                         if (line.Length > 0 || segment.IsLineBreak) | ||||
|                         if (line.Width > 0 || segment.IsLineBreak) | ||||
|                         { | ||||
|                             lines.Add(line); | ||||
|                             line = new SegmentLine(); | ||||
| @@ -184,7 +198,7 @@ namespace Spectre.Console.Composition | ||||
|  | ||||
|                         if (parts.Length > 1) | ||||
|                         { | ||||
|                             if (line.Length > 0) | ||||
|                             if (line.Width > 0) | ||||
|                             { | ||||
|                                 lines.Add(line); | ||||
|                                 line = new SegmentLine(); | ||||
| @@ -211,5 +225,21 @@ namespace Spectre.Console.Composition | ||||
|  | ||||
|             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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,18 +1,38 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a line of segments. | ||||
|     /// Represents a collection of segments. | ||||
|     /// </summary> | ||||
|     [SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix")] | ||||
|     public sealed class SegmentLine : List<Segment> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the length of the line. | ||||
|         /// Gets the width of the line. | ||||
|         /// </summary> | ||||
|         public int Length => this.Sum(line => line.Text.Length); | ||||
|         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/Composition/SegmentLineEnumerator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/Spectre.Console/Composition/SegmentLineEnumerator.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     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/Composition/SegmentLineIterator.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/Spectre.Console/Composition/SegmentLineIterator.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| using System.Collections; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console.Composition | ||||
| { | ||||
|     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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,216 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents text with color and decorations. | ||||
|     /// </summary> | ||||
|     [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||
|     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 = _text.SplitLines(); | ||||
|             return lines.Max(x => x.CellLength(encoding)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public IEnumerable<Segment> Render(Encoding encoding, int width) | ||||
|         { | ||||
|             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 Queue<Segment>(segments); | ||||
|  | ||||
|             while (queue.Count > 0) | ||||
|             { | ||||
|                 var segment = queue.Dequeue(); | ||||
|  | ||||
|                 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.Enqueue(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.Add((0, false, 0)); | ||||
|             spans.AddRange(_spans.SelectIndex((span, index) => (span.Start, false, index + 1))); | ||||
|             spans.AddRange(_spans.SelectIndex((span, index) => (span.End, true, index + 1))); | ||||
|             spans.Add((_text.Length, true, 0)); | ||||
|             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)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										141
									
								
								src/Spectre.Console/Composition/Widgets/Grid.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/Spectre.Console/Composition/Widgets/Grid.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| 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 | ||||
|             { | ||||
|                 Border = BorderKind.None, | ||||
|                 ShowHeaders = false, | ||||
|                 IsGrid = true, | ||||
|                 PadRightCell = false, | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public Measurement Measure(RenderContext context, int maxWidth) | ||||
|         { | ||||
|             return ((IRenderable)_table).Measure(context, maxWidth); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public 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 string[_table.ColumnCount]; | ||||
|             Enumerable.Range(0, _table.ColumnCount).ForEach(index => columns[index] = string.Empty); | ||||
|             AddRow(columns); | ||||
|         } | ||||
|  | ||||
|         /// <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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/Spectre.Console/Composition/Widgets/GridColumn.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Spectre.Console/Composition/Widgets/GridColumn.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a grid column. | ||||
|     /// </summary> | ||||
|     public sealed class GridColumn | ||||
|     { | ||||
|         /// <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; } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										133
									
								
								src/Spectre.Console/Composition/Widgets/Panel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/Spectre.Console/Composition/Widgets/Panel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Composition; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a panel which contains another renderable item. | ||||
|     /// </summary> | ||||
|     public sealed class Panel : IRenderable | ||||
|     { | ||||
|         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="content">The panel content.</param> | ||||
|         public Panel(IRenderable content) | ||||
|         { | ||||
|             _child = content ?? throw new System.ArgumentNullException(nameof(content)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         Measurement IRenderable.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/> | ||||
|         IEnumerable<Segment> IRenderable.Render(RenderContext context, int width) | ||||
|         { | ||||
|             var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); | ||||
|  | ||||
|             var paddingWidth = Padding.GetHorizontalPadding(); | ||||
|             var childWidth = width - EdgeWidth - paddingWidth; | ||||
|  | ||||
|             if (!Expand) | ||||
|             { | ||||
|                 var measurement = _child.Measure(context, width - 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)), | ||||
|                 new Segment("\n"), | ||||
|             }; | ||||
|  | ||||
|             // 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(new Segment("\n")); | ||||
|             } | ||||
|  | ||||
|             // 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(new Segment("\n")); | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										125
									
								
								src/Spectre.Console/Composition/Widgets/Table.Calculations.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/Spectre.Console/Composition/Widgets/Table.Calculations.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| 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 = ((IRenderable)column.Text).Measure(options, maxWidth); | ||||
|             minWidths.Add(measure.Min); | ||||
|             maxWidths.Add(measure.Max); | ||||
|  | ||||
|             foreach (var row in rows) | ||||
|             { | ||||
|                 measure = ((IRenderable)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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										386
									
								
								src/Spectre.Console/Composition/Widgets/Table.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								src/Spectre.Console/Composition/Widgets/Table.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,386 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a table. | ||||
|     /// </summary> | ||||
|     public sealed partial class Table : IRenderable | ||||
|     { | ||||
|         private readonly List<TableColumn> _columns; | ||||
|         private readonly List<List<Text>> _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<Text>>(); | ||||
|         } | ||||
|  | ||||
|         /// <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)); | ||||
|             } | ||||
|  | ||||
|             AddColumn(new TableColumn(column)); | ||||
|         } | ||||
|  | ||||
|         /// <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 string[] columns) | ||||
|         { | ||||
|             if (columns is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(columns)); | ||||
|             } | ||||
|  | ||||
|             foreach (var column in columns) | ||||
|             { | ||||
|                 AddColumn(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 string[ColumnCount]; | ||||
|             Enumerable.Range(0, ColumnCount).ForEach(index => columns[index] = string.Empty); | ||||
|             AddRow(columns); | ||||
|         } | ||||
|  | ||||
|         /// <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.Markup(column)).ToList()); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         Measurement IRenderable.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/> | ||||
|         IEnumerable<Segment> IRenderable.Render(RenderContext context, int width) | ||||
|         { | ||||
|             if (context is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(context)); | ||||
|             } | ||||
|  | ||||
|             var border = Composition.Border.GetBorder(Border, (context.LegacyConsole || !context.Unicode) && SafeBorder); | ||||
|  | ||||
|             var showBorder = Border != BorderKind.None; | ||||
|             var hideBorder = Border == BorderKind.None; | ||||
|             var hasRows = _rows.Count > 0; | ||||
|  | ||||
|             var maxWidth = width; | ||||
|             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. | ||||
|             width = columnWidths.Sum() + GetExtraWidth(includePadding: true); | ||||
|  | ||||
|             var rows = new List<List<Text>>(); | ||||
|             if (ShowHeaders) | ||||
|             { | ||||
|                 // Add columns to top of rows | ||||
|                 rows.Add(new List<Text>(_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(((IRenderable)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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/Spectre.Console/Composition/Widgets/TableColumn.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/Spectre.Console/Composition/Widgets/TableColumn.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a table column. | ||||
|     /// </summary> | ||||
|     public sealed class TableColumn | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the text associated with the column. | ||||
|         /// </summary> | ||||
|         public Text 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) | ||||
|         { | ||||
|             Text = Text.Markup(text ?? throw new ArgumentNullException(nameof(text))); | ||||
|             Width = null; | ||||
|             Padding = new Padding(1, 1); | ||||
|             NoWrap = false; | ||||
|             Alignment = null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										270
									
								
								src/Spectre.Console/Composition/Widgets/Text.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								src/Spectre.Console/Composition/Widgets/Text.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,270 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Linq; | ||||
| using Spectre.Console.Composition; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a piece of text. | ||||
|     /// </summary> | ||||
|     [DebuggerDisplay("{_text,nq}")] | ||||
|     [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||
|     public sealed class Text : IRenderable | ||||
|     { | ||||
|         private readonly List<SegmentLine> _lines; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets or sets the text alignment. | ||||
|         /// </summary> | ||||
|         public Justify Alignment { get; set; } = Justify.Left; | ||||
|  | ||||
|         /// <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/> | ||||
|         public 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/> | ||||
|         public 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 justification = context.Justification ?? Alignment; | ||||
|  | ||||
|             var lines = SplitLines(context, maxWidth); | ||||
|             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 (string.IsNullOrEmpty(current) && last) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/Spectre.Console/Composition/Widgets/TextExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/Spectre.Console/Composition/Widgets/TextExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="Text"/>. | ||||
|     /// </summary> | ||||
|     public static class TextExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Sets the text alignment. | ||||
|         /// </summary> | ||||
|         /// <param name="text">The <see cref="Text"/> instance.</param> | ||||
|         /// <param name="alignment">The text alignment.</param> | ||||
|         /// <returns>The same <see cref="Text"/> instance.</returns> | ||||
|         public static Text WithAlignment(this Text text, Justify alignment) | ||||
|         { | ||||
|             if (text is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(text)); | ||||
|             } | ||||
|  | ||||
|             text.Alignment = alignment; | ||||
|             return text; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -26,17 +26,26 @@ namespace Spectre.Console | ||||
|                 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); | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -32,12 +32,12 @@ namespace Spectre.Console.Internal | ||||
|             new Regex("bvterm"), // Bitvise SSH Client | ||||
|         }; | ||||
|  | ||||
|         public static bool Detect(bool upgrade) | ||||
|         public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool upgrade) | ||||
|         { | ||||
|             // Github action doesn't setup a correct PTY but supports ANSI. | ||||
|             if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTION"))) | ||||
|             { | ||||
|                 return true; | ||||
|                 return (true, false); | ||||
|             } | ||||
|  | ||||
|             // Running on Windows? | ||||
| @@ -47,10 +47,11 @@ namespace Spectre.Console.Internal | ||||
|                 var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI"); | ||||
|                 if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     return true; | ||||
|                     return (true, false); | ||||
|                 } | ||||
|  | ||||
|                 return Windows.SupportsAnsi(upgrade); | ||||
|                 var supportsAnsi = Windows.SupportsAnsi(upgrade, out var legacyConsole); | ||||
|                 return (supportsAnsi, legacyConsole); | ||||
|             } | ||||
|  | ||||
|             // Check if the terminal is of type ANSI/VT100/xterm compatible. | ||||
| @@ -59,11 +60,11 @@ namespace Spectre.Console.Internal | ||||
|             { | ||||
|                 if (_regexes.Any(regex => regex.IsMatch(term))) | ||||
|                 { | ||||
|                     return true; | ||||
|                     return (true, false); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|             return (false, true); | ||||
|         } | ||||
|  | ||||
|         [SuppressMessage("Design", "CA1060:Move pinvokes to native methods class")] | ||||
| @@ -91,8 +92,10 @@ namespace Spectre.Console.Internal | ||||
|             public static extern uint GetLastError(); | ||||
|  | ||||
|             [SuppressMessage("Design", "CA1031:Do not catch general exception types")] | ||||
|             public static bool SupportsAnsi(bool upgrade) | ||||
|             public static bool SupportsAnsi(bool upgrade, out bool isLegacy) | ||||
|             { | ||||
|                 isLegacy = false; | ||||
|  | ||||
|                 try | ||||
|                 { | ||||
|                     var @out = GetStdHandle(STD_OUTPUT_HANDLE); | ||||
| @@ -104,6 +107,8 @@ namespace Spectre.Console.Internal | ||||
|  | ||||
|                     if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) | ||||
|                     { | ||||
|                         isLegacy = true; | ||||
|  | ||||
|                         if (!upgrade) | ||||
|                         { | ||||
|                             return false; | ||||
|   | ||||
| @@ -41,12 +41,12 @@ namespace Spectre.Console.Internal | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public AnsiConsoleRenderer(TextWriter @out, ColorSystem system) | ||||
|         public AnsiConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole) | ||||
|         { | ||||
|             _out = @out ?? throw new ArgumentNullException(nameof(@out)); | ||||
|             _system = system; | ||||
|  | ||||
|             Capabilities = new Capabilities(true, system); | ||||
|             Capabilities = new Capabilities(true, system, legacyConsole); | ||||
|             Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8; | ||||
|             Foreground = Color.Default; | ||||
|             Background = Color.Default; | ||||
| @@ -60,12 +60,19 @@ namespace Spectre.Console.Internal | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             _out.Write(AnsiBuilder.GetAnsi( | ||||
|                 _system, | ||||
|                 text.NormalizeLineEndings(native: true), | ||||
|                 Decoration, | ||||
|                 Foreground, | ||||
|                 Background)); | ||||
|             var parts = text.NormalizeLineEndings().Split(new[] { '\n' }); | ||||
|             foreach (var (_, _, last, part) in parts.Enumerate()) | ||||
|             { | ||||
|                 if (!string.IsNullOrEmpty(part)) | ||||
|                 { | ||||
|                     _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) | ||||
|                 { | ||||
|                     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); | ||||
|                     if (match.Success && int.TryParse(match.Groups["major"].Value, out var major)) | ||||
|                     { | ||||
|   | ||||
| @@ -45,7 +45,7 @@ namespace Spectre.Console.Internal | ||||
|             return ColorPalette.EightBit[number]; | ||||
|         } | ||||
|  | ||||
|         public static string GetName(int number) | ||||
|         public static string? GetName(int number) | ||||
|         { | ||||
|             _nameLookup.TryGetValue(number, out var name); | ||||
|             return name; | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
| @@ -13,9 +14,41 @@ namespace Spectre.Console.Internal | ||||
|  | ||||
|             var buffer = settings.Out ?? System.Console.Out; | ||||
|  | ||||
|             var supportsAnsi = settings.Ansi == AnsiSupport.Detect | ||||
|                 ? AnsiDetector.Detect(true) | ||||
|                 : settings.Ansi == AnsiSupport.Yes; | ||||
|             var supportsAnsi = settings.Ansi == AnsiSupport.Yes; | ||||
|             var legacyConsole = false; | ||||
|  | ||||
|             if (settings.Ansi == AnsiSupport.Detect) | ||||
|             { | ||||
|                 (supportsAnsi, legacyConsole) = AnsiDetector.Detect(true); | ||||
|  | ||||
|                 // Check whether or not this is a legacy console from the existing instance (if any). | ||||
|                 // We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING | ||||
|                 // on Windows, there is no way of detecting whether or not we're running on a legacy console or not. | ||||
|                 if (AnsiConsole.Created && !legacyConsole && buffer.IsStandardOut() && AnsiConsole.Capabilities.LegacyConsole) | ||||
|                 { | ||||
|                     legacyConsole = AnsiConsole.Capabilities.LegacyConsole; | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (buffer.IsStandardOut()) | ||||
|                 { | ||||
|                     // Are we running on Windows? | ||||
|                     if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||||
|                     { | ||||
|                         // Not the first console we're creating? | ||||
|                         if (AnsiConsole.Created) | ||||
|                         { | ||||
|                             legacyConsole = AnsiConsole.Capabilities.LegacyConsole; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             // Try detecting whether or not this | ||||
|                             (_, legacyConsole) = AnsiDetector.Detect(false); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect | ||||
|                 ? ColorSystemDetector.Detect(supportsAnsi) | ||||
| @@ -23,13 +56,13 @@ namespace Spectre.Console.Internal | ||||
|  | ||||
|             if (supportsAnsi) | ||||
|             { | ||||
|                 return new AnsiConsoleRenderer(buffer, colorSystem) | ||||
|                 return new AnsiConsoleRenderer(buffer, colorSystem, legacyConsole) | ||||
|                 { | ||||
|                     Decoration = Decoration.None, | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             return new FallbackConsoleRenderer(buffer, colorSystem); | ||||
|             return new FallbackConsoleRenderer(buffer, colorSystem, legacyConsole); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,14 +15,18 @@ namespace Spectre.Console.Internal | ||||
|             { | ||||
|                 { "none", Decoration.None }, | ||||
|                 { "bold", Decoration.Bold }, | ||||
|                 { "b", Decoration.Bold }, | ||||
|                 { "dim", Decoration.Dim }, | ||||
|                 { "italic", Decoration.Italic }, | ||||
|                 { "i", Decoration.Italic }, | ||||
|                 { "underline", Decoration.Underline }, | ||||
|                 { "u", Decoration.Underline }, | ||||
|                 { "invert", Decoration.Invert }, | ||||
|                 { "conceal", Decoration.Conceal }, | ||||
|                 { "slowblink", Decoration.SlowBlink }, | ||||
|                 { "rapidblink", Decoration.RapidBlink }, | ||||
|                 { "strikethrough", Decoration.Strikethrough }, | ||||
|                 { "s", Decoration.Strikethrough }, | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,19 @@ namespace Spectre.Console.Internal | ||||
| { | ||||
|     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) | ||||
|         { | ||||
|             if (source is null) | ||||
| @@ -40,5 +53,18 @@ namespace Spectre.Console.Internal | ||||
|         { | ||||
|             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.Collections.Generic; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| @@ -17,9 +18,9 @@ namespace Spectre.Console.Internal | ||||
|  | ||||
|         public static string NormalizeLineEndings(this string text, bool native = false) | ||||
|         { | ||||
|             var normalized = text?.Replace("\r\n", "\n") | ||||
|                 ?.Replace("\r", string.Empty); | ||||
|             text ??= string.Empty; | ||||
|  | ||||
|             var normalized = text?.Replace("\r\n", "\n")?.Replace("\r", string.Empty) ?? string.Empty; | ||||
|             if (native && !_alreadyNormalized) | ||||
|             { | ||||
|                 normalized = normalized.Replace("\n", Environment.NewLine); | ||||
| @@ -30,7 +31,52 @@ namespace Spectre.Console.Internal | ||||
|  | ||||
|         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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -85,7 +85,7 @@ namespace Spectre.Console.Internal | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public FallbackConsoleRenderer(TextWriter @out, ColorSystem system) | ||||
|         public FallbackConsoleRenderer(TextWriter @out, ColorSystem system, bool legacyConsole) | ||||
|         { | ||||
|             _out = @out; | ||||
|             _system = system; | ||||
| @@ -105,7 +105,7 @@ namespace Spectre.Console.Internal | ||||
|                 Encoding = Encoding.UTF8; | ||||
|             } | ||||
|  | ||||
|             Capabilities = new Capabilities(false, _system); | ||||
|             Capabilities = new Capabilities(false, _system, legacyConsole); | ||||
|         } | ||||
|  | ||||
|         public void Write(string text) | ||||
|   | ||||
| @@ -1,15 +1,16 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     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; | ||||
|  | ||||
|             var result = new Text(string.Empty); | ||||
|             var result = new Text(); | ||||
|             using var tokenizer = new MarkupTokenizer(text); | ||||
|  | ||||
|             var stack = new Stack<Style>(); | ||||
| @@ -17,6 +18,10 @@ namespace Spectre.Console.Internal | ||||
|             while (tokenizer.MoveNext()) | ||||
|             { | ||||
|                 var token = tokenizer.Current; | ||||
|                 if (token == null) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 if (token.Kind == MarkupTokenKind.Open) | ||||
|                 { | ||||
| @@ -35,7 +40,7 @@ namespace Spectre.Console.Internal | ||||
|                 else if (token.Kind == MarkupTokenKind.Text) | ||||
|                 { | ||||
|                     // Get the effecive style. | ||||
|                     var effectiveStyle = style.Combine(stack); | ||||
|                     var effectiveStyle = style.Combine(stack.Reverse()); | ||||
|                     result.Append(token.Value, effectiveStyle); | ||||
|                 } | ||||
|                 else | ||||
|   | ||||
| @@ -7,7 +7,7 @@ namespace Spectre.Console.Internal | ||||
|     { | ||||
|         private readonly StringBuffer _reader; | ||||
|  | ||||
|         public MarkupToken Current { get; private set; } | ||||
|         public MarkupToken? Current { get; private set; } | ||||
|  | ||||
|         public MarkupTokenizer(string text) | ||||
|         { | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.IO; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class StringBuffer : IDisposable | ||||
|     { | ||||
|         [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "False positive")] | ||||
|         private readonly StringReader _reader; | ||||
|         private readonly int _length; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Globalization; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
| @@ -12,21 +14,23 @@ namespace Spectre.Console.Internal | ||||
|                 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; | ||||
|         } | ||||
|  | ||||
|         public static bool TryParse(string text, out Style style) | ||||
|         public static bool TryParse(string text, out Style? style) | ||||
|         { | ||||
|             style = Parse(text, out var error); | ||||
|             if (error != null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
|             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 effectiveForeground = (Color?)null; | ||||
| @@ -62,16 +66,30 @@ namespace Spectre.Console.Internal | ||||
|                     var color = ColorTable.GetColor(part); | ||||
|                     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 | ||||
|                         { | ||||
|                             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) | ||||
| @@ -100,5 +118,82 @@ namespace Spectre.Console.Internal | ||||
|             error = null; | ||||
|             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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,30 +2,29 @@ | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>netstandard2.0</TargetFramework> | ||||
|     <Nullable>enable</Nullable> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <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> | ||||
|  | ||||
|     <None Include="../../gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="System.Memory" Version="4.5.4" /> | ||||
|   </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> | ||||
|   | ||||
| @@ -57,6 +57,15 @@ namespace Spectre.Console | ||||
|             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> | ||||
|         /// Converts the string representation of a style to its <see cref="Style"/> equivalent. | ||||
|         /// 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. | ||||
|         /// </param> | ||||
|         /// <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); | ||||
|         } | ||||
| @@ -113,13 +122,13 @@ namespace Spectre.Console | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override bool Equals(object obj) | ||||
|         public override bool Equals(object? obj) | ||||
|         { | ||||
|             return Equals(obj as Style); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public bool Equals(Style other) | ||||
|         public bool Equals(Style? other) | ||||
|         { | ||||
|             if (other == null) | ||||
|             { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user