Compare commits

..

86 Commits

Author SHA1 Message Date
Patrik Svensson
7397169a27 Update Cake to 4.0.0 2023-11-20 13:14:40 +01:00
Patrik Svensson
ffdb47d77f Build generator as part of build 2023-11-20 13:14:40 +01:00
Patrik Svensson
383bee0e3e Do not push packages to GitHub 2023-11-19 12:18:22 +01:00
Patrik Svensson
4d6541dd14 Add back net7.0 TFM for tests 2023-11-19 12:18:22 +01:00
Patrik Svensson
b1e0896a0d Add net8.0 support 2023-11-14 22:24:53 +01:00
Will Baldoumas
e07ccd9f66 Allow Confirmation Prompt Styling (#1210)
* Allow Confirmation Prompt Styling

* Remove style null check

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>

* Remove style null coalesce

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>

* Update comment

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>

* Remove style null check

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>

* Update comment

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>

* Remove style null coalesce

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>

---------

Co-authored-by: Cédric Luthi <cedric.luthi@gmail.com>
2023-11-13 18:23:09 +01:00
Frank Ray
250b1f4c9c Add localization support to help provider (#1349)
Closes #1349
2023-11-11 00:22:52 +01:00
Nils Andresen
023c77ff09 fixed the build-errors
All of which I introduced earlier.
2023-11-11 00:09:08 +01:00
Nils Andresen
dc402220f2 (#1313) fixed errors in FakeTypeRegistrar and FakeTypeResolver
to make the unit tests pass.
2023-11-11 00:09:08 +01:00
Nils Andresen
c448d0d5f6 (#1313) Add TypeRegistrarBaseTests for the FakeTypeRegistrar
So it also works according to our assumptions.
2023-11-11 00:09:08 +01:00
Nils Andresen
3da367f29f (#1313) fix mixing of Registrations
i.e. combining Register, RegisterInstance and RegisterLazy
2023-11-11 00:09:08 +01:00
Nils Andresen
ead7115cbe (#1313) Add new test to TypeRegistrarBaseTests
to assert the assumptions we're making in the code.
2023-11-11 00:09:08 +01:00
Nils Andresen
1fd028942f (#1313) removed the default registration of the IHelpProvider 2023-11-11 00:09:08 +01:00
Cédric Luthi
4219bbbf61 Allow passing a nullable style in DefaultValueStyle() and ChoicesStyle()
This will allow to slightly simplify the implementation of #1210

See also related discussion on https://github.com/spectreconsole/spectre.console/pull/1349#discussion_r1388385384
2023-11-10 08:01:18 +01:00
Cédric Luthi
29a43686d4 Fix AnsiConsoleOutput safe height
The safe height (introduced in 3e2eea730b) would be 80 (the value of the safe width) instead of 24.

Removing the explicit `defaultValue` parameter ensures that the correct constants are used, i.e. Constants.DefaultTerminalWidth and Constants.DefaultTerminalHeight.
2023-11-09 12:11:56 +01:00
Patrik Svensson
63b940cf0e Merge pull request #1338 from nils-a/feature/GH-1188 2023-10-19 11:12:09 +02:00
Nils Andresen
bbf58ee814 (#1188) Rows measure greedy 2023-10-19 07:46:12 +02:00
Patrik Svensson
e2a674815d Merge pull request #1318 from nils-a/feature/GH-1317 2023-09-27 08:57:52 +02:00
Nils Andresen
343b98944d added a minimal PR template 2023-09-27 08:28:08 +02:00
Andrew Rathbun
2bbb7c1ab6 Update Showcase.csproj - minor typo (#1315) 2023-09-23 15:55:56 +02:00
Cédric Luthi
5296e56b1c Fix DefaultValue for FileInfo and DirectoryInfo (#1238)
Commit d3f4f5f208 introduced automatic conversion to FileInfo and DirectoryInfo but failed to properly handle the conversion if the value comes from the [DefaultValue] attribute.

Using both `var (converter, stringConstructor) = GetConverter(...)` and `var (converter, _) = GetConverter(...)` should have been a red flag!
2023-09-21 12:05:42 +01:00
Patrik Svensson
943c045fab Fixes TextPath rendering bugs (#1308)
* Do not emit line break when rendering
* Don't be greedy when measuring.
* Fix a condition that decides if the path fits in the allotted space.

Closes #1307
2023-09-18 08:04:57 +02:00
Fraser Waters
2af3f7faeb Add an example showing the decorations off (#1191) 2023-09-16 23:08:52 +02:00
Phil Scott
ed9e198d60 Progress bar header and footer (#1262) 2023-09-16 22:39:43 +02:00
Patrik Svensson
3bee7212b7 Merge pull request #1303 from nils-a/feature/GH-684 2023-09-16 19:37:00 +02:00
Patrik Svensson
c82d8c4523 Add option to show separator between table rows (#1304)
* Add option to show separator between table rows
* Panels should show header if borders are not shown

Closes #835
2023-09-16 18:49:12 +02:00
Nils Andresen
bef21e8a21 (#684) Enable setting the color of the values in a BreakdownChart 2023-09-15 20:12:06 +02:00
Ola Bäcker
037e109699 Fix figlet centering possibly throwing due to negative size (#1302) 2023-09-14 19:50:12 +02:00
Nils Andresen
f7befacd79 (#644) Specified settings for the argument vector (#1301) 2023-09-13 09:06:25 +02:00
Fraser Waters
cec5fb4595 Render tables with zero-width columns (#1197)
Fixes https://github.com/spectreconsole/spectre.console/issues/361
2023-09-12 15:46:25 +02:00
Nils Andresen
9c86391fb6 (#1297) change all SetErrorHandler to SetExceptionHandler (#1298) 2023-09-12 13:57:02 +02:00
Nils Andresen
a3dcb31729 Update ColumnsSample to showcase nicer data (#1295)
Also, I switched the base width of the AsciiCast back
to 82 (from 120) so newly created casts are no longer
overflowing the display with on the pages.

Re-created the Panel and BreakdownChart casts, as
they were currently overflowing due to the 120 chars
width of the cast.
2023-09-12 12:58:18 +02:00
Patrik Svensson
1002c6fe27 Merge pull request #1294 from nils-a/feature/GH-1279 2023-09-11 23:45:39 +02:00
Nils Andresen
c64797d681 (#1279) added the missing columns-cast 2023-09-11 23:26:54 +02:00
Frank Ray
131b37fff8 Allow custom help providers (#1259)
Allow custom help providers

* Version option will show in help even with a default command

* Reserve `-v` and `--version` as special Spectre.Console command line arguments (nb. breaking change for Spectre.Console users who have a default command with a settings class that uses either of these switches).

* Help writer correctly determines if trailing commands exist and whether to display them as optional or mandatory in the usage statement.

* Ability to control the number of indirect commands to display in the help text when the command itself doesn't have any examples of its own. Defaults to 5 (for backward compatibility) but can be set to any integer or zero to disable completely.

* Significant increase in unit test coverage for the help writer.

* Minor grammatical improvements to website documentation.
2023-09-08 09:51:33 +02:00
Tomasz Prasołek
813a53cdfa Fix Rule widget docs 2023-07-16 12:29:04 +02:00
Cédric Luthi
2af901a814 Remove unnecessary [NotNull] attributes
When subclassing `Command<TSettings>` which has the [NotNull] attributes, Rider/ReSharper gives this warning:
> Nullability of type of parameter 'context' in method does not match overridden member `int Spectre.Console.Cli.Command<TSettings>.Execute(CommandContext, TSettings)` (possibly because of nullability attributes)

When subclassing `Command<TSettings>` which does not have the [NotNull] attributes, Rider/ReSharper gives this warning:
> The nullability attribute has no effect and can be safely removed

The solution is simply to remove the [NotNull] attributes.

Since `<Nullable>enable</Nullable>` is set in the project, they are actually not necessary. By the way, the non-generic `Command` class does not have the [NotNull] attributes.
2023-07-12 15:26:12 +02:00
Cédric Luthi
83afa97017 Set end_of_line to LF instead of CRLF
This matches the actual content of the repository (except for a few files which have CRLF instead of LF)

The motivation behind this change is that Rider observes the .editorconfig rules and changes the line ending to CRLF on save. When submitting pull requests the diff is full of changes because all the end of lines were changed from LF to CRLF. By setting `end_of_line` to `LF` the end of lines are not changed on save and the diffs are clean when submitting pull requests.
2023-07-12 15:25:52 +02:00
Jeppe Roi Kristensen
e0ded712e8 Add fix to avoid exception on Rows with no children 2023-06-13 00:36:06 +02:00
Cédric Luthi
d484e832f5 Relax the SDK requirements by rolling forward to the latest feature
This was initially introduced in 3c5b98123b by Daniel Cazzulino, then reverted in 82de4a55c4 by Patrick Svensson because of a bug in the .NET 6.0.401 SDK.
2023-06-07 13:41:09 +02:00
fredrikbentzen
71a5d83067 Fixed render issue where writeline inside status caused corrupt output #415 #694 2023-06-04 20:31:12 +02:00
Ignacio Calvo
35ce60b596 Implemented AddAsyncDelegate (#766) 2023-05-25 11:31:01 +01:00
Frank Ray
0ec70a44db Async command unit tests 2023-05-19 17:19:17 +01:00
Patrik Svensson
1ee2653cf8 Merge pull request #1231 from FrankRay78/Blog-post-for-0.47.0
Blog post for 0.47.0 (updated)
2023-05-19 18:05:06 +02:00
Frank Ray
4c0178cf9a Merge branch 'main' into Blog-post-for-0.47.0 2023-05-19 16:50:48 +01:00
Frank Ray
296bc61837 Updated links and github usernames to markdown url format so they render as native html links 2023-05-19 16:48:48 +01:00
Frank Ray
766ccb1f1b Minor grammatical update 2023-05-19 16:40:57 +02:00
Frank Ray
f7314dc2e8 Blog post for 0.47.0 2023-05-19 16:40:57 +02:00
Frank Ray
f453890d13 Minor grammatical update 2023-05-19 12:26:35 +01:00
Frank Ray
2c8f459806 Blog post for 0.47.0 2023-05-19 12:22:28 +01:00
Elisha Aguilera
018f4ebd17 Minor refactors (#1081) 2023-05-14 15:30:27 +01:00
Andrii Rublov
404b052a5f Add ability to pass example args using params syntax (#1166) 2023-05-12 12:08:42 +01:00
Cédric Luthi
dac2097321 Add support for arrays in [DefaultValue] attributes (#1164)
Fixes #1163
2023-05-11 14:26:53 +01:00
Cédric Luthi
6acf9b8c63 Add an implicit operator to convert from Color to Style (#1160) 2023-05-10 14:20:12 +01:00
Patrik Svensson
3ec0fee795 Merge pull request #1218 from phillip-haydon/patch-1 2023-04-24 08:56:25 +02:00
Phillip Haydon
5843a4545e Fix coconut spelling 2023-04-24 14:37:06 +08:00
Patrik Svensson
d64d3ec770 Merge pull request #1211 from MaxAtoms/main 2023-04-03 16:32:18 +02:00
Thomas M. Schöller
4dcbd30285 Merge branch 'spectreconsole:main' into main 2023-04-03 12:45:34 +02:00
MaxAtoms
1334319dd1 Add Alacritty as supported Ansi console 2023-04-03 12:42:24 +02:00
Frank Ray
714cf179cb Command line improvements (#1103)
Closes #187
Closes #203
Closes #1059
2023-04-02 22:43:21 +02:00
Patrik Svensson
70da3f40ff Merge pull request #1161 from MartinZikmund/dev/mazi/stringcomparer-confirmation 2023-03-16 09:57:15 +01:00
Martin Zikmund
5075732564 Adjustments based on code review 2023-03-16 09:45:42 +01:00
Gérald Barré
10467a2912 Do not register analyzers if SpectreConsole is not available in the current compilation 2023-03-13 20:07:41 -04:00
Gérald Barré
a7ab1b690e Use SymbolEqualityComparer.Default when possible 2023-03-13 20:03:12 -04:00
Gérald Barré
142942b8a2 Simplify access to the SemanticModel in analyzers 2023-03-13 19:58:40 -04:00
Gérald Barré
0bab835293 Forward CancellationToken to GetOperation 2023-03-13 19:57:08 -04:00
Patrik Svensson
a6af9a6842 Merge pull request #1183 from Frassle/promptExampleTypo 2023-03-01 18:43:46 +01:00
Cédric Luthi
d3f4f5f208 Add support for converting command parameters into FileInfo and DirectoryInfo (#1145)
Add support for converting command parameters that doesn't have a built-in TypeConverter but has a constructor that takes a string. For CLI apps, FileInfo and DirectoryInfo will likely be the most useful ones, but there may be others.
2023-03-01 12:02:43 +00:00
Fraser Waters
cbbdb3369c Fix minor typo in Prompt example 2023-02-26 22:22:31 +00:00
Gérald Barré
6740f0b02b Add support for static lambda and delegate 2023-02-24 19:04:01 -05:00
Gérald Barré
f7f99ec899 Simplify and make the code fix more robust 2023-02-24 19:04:01 -05:00
Gérald Barré
955fe07bac Allow to apply code fix in top-level statements 2023-02-24 19:04:01 -05:00
Patrik Svensson
819b948e78 Merge pull request #1174 from meziantou/missing-stringcomparison 2023-02-22 16:03:29 +01:00
Gérald Barré
baa8220a52 Use StringComparison.Ordinal instead of culture-sensitive comparisons 2023-02-20 20:23:06 -05:00
Martin Zikmund
9cd7b24e65 Allow configuration of confirmation prompt comparison via StringComparer 2023-02-11 17:09:16 +01:00
Ilya Hryapko
04610cf492 Alias for command branches (#411) 2023-02-09 14:26:06 +00:00
Patrik Svensson
f4183e0462 Merge pull request #1152 from MartinZikmund/dev/mazi/confirmation-insensitive 2023-02-04 20:44:34 +01:00
Martin Zikmund
29846ba505 Ensure correct comparer is used for TextPrompt 2023-02-04 19:56:37 +01:00
Patrik Svensson
92318ce1fb Merge pull request #1151 from MartinZikmund/dev/mazi/confirmation-insensitive 2023-02-04 13:41:14 +01:00
Martin Zikmund
720db951f3 feat: Allow case-insensitive confirmation prompt 2023-02-04 13:24:11 +01:00
Patrik Svensson
e042fe15e4 Merge pull request #1143 from wbaldoumas/alignment-vs-justification-docs-fixes 2023-01-26 03:38:25 +01:00
Will Baldoumas
62b30d6072 Alignment => Justification Docs Fixes 2023-01-25 17:45:30 -08:00
Patrik Svensson
beca0b2419 Merge pull request #1141 from 0xced/better-conversion-errors 2023-01-24 20:15:02 +01:00
Cédric Luthi
de847b90e4 Improve conversion error messages
When a conversion to an enum fails, list all the valid enum values in the error message.

Message before this commit:
> Error: heimday is not a valid value for DayOfWeek.

Message after this commit:
> Error: Failed to convert 'heimday' to DayOfWeek. Valid values are 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
2023-01-24 19:40:11 +01:00
Cédric Luthi
7b9553dd22 Add possibility to set description and/or data for the default command (#1091) 2023-01-12 12:12:27 +00:00
Patrik Svensson
f223f6061c Add blog post for 0.46 release 2023-01-10 04:00:15 +01:00
291 changed files with 7492 additions and 2196 deletions

View File

@@ -2,7 +2,7 @@ root = true
[*]
charset = utf-8
end_of_line = CRLF
end_of_line = LF
indent_style = space
indent_size = 4
insert_final_newline = false
@@ -26,9 +26,6 @@ indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.sh]
end_of_line = lf
[*.cs]
# Prefer file scoped namespace declarations
csharp_style_namespace_declarations = file_scoped:warning

19
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,19 @@
<!--
Do NOT open a PR without discussing the changes on an open issue, first.
Add the issue number here. e.g. #123
-->
fixes #
<!-- formalities. These are not optional. -->
- [ ] I have read the [Contribution Guidelines](../CONTRIBUTING.md)
- [ ] I have commented on the issue above and discussed the intended changes
- [ ] A maintainer has signed off on the changes and the issue was assigned to me
- [ ] All newly added code is adequately covered by tests
- [ ] All existing tests are still running without errors
- [ ] The documentation was modified to reflect the changes _OR_ no documentation changes are required.
## Changes
<!-- describe the changes you made. -->

View File

@@ -76,6 +76,11 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
- name: Build
shell: bash

View File

@@ -107,6 +107,11 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
- name: Publish
shell: bash

View File

@@ -14,8 +14,21 @@ Task("Build")
.IsDependentOn("Clean")
.Does(context =>
{
Information("Compiling generator...");
DotNetBuild("./resources/scripts/Generator/Generator.sln", new DotNetBuildSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
});
Information("\nCompiling Spectre.Console...");
DotNetBuild("./src/Spectre.Console.sln", new DotNetBuildSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
@@ -28,6 +41,8 @@ Task("Build-Analyzer")
{
DotNetBuild("./src/Spectre.Console.Analyzer.sln", new DotNetBuildSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
@@ -40,6 +55,8 @@ Task("Build-Examples")
{
DotNetBuild("./examples/Examples.sln", new DotNetBuildSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetMSBuildSettings()
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
@@ -54,18 +71,24 @@ Task("Test")
{
DotNetTest("./test/Spectre.Console.Tests/Spectre.Console.Tests.csproj", new DotNetTestSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoRestore = true,
NoBuild = true,
});
DotNetTest("./test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj", new DotNetTestSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoRestore = true,
NoBuild = true,
});
DotNetTest("./test/Spectre.Console.Analyzer.Tests/Spectre.Console.Analyzer.Tests.csproj", new DotNetTestSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoRestore = true,
NoBuild = true,
});
@@ -77,6 +100,8 @@ Task("Package")
{
context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoRestore = true,
NoBuild = true,
OutputDirectory = "./.artifacts",
@@ -86,6 +111,8 @@ Task("Package")
context.DotNetPack($"./src/Spectre.Console.Analyzer.sln", new DotNetPackSettings {
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
NoLogo = true,
NoRestore = true,
NoBuild = true,
OutputDirectory = "./.artifacts",
@@ -94,38 +121,6 @@ Task("Package")
});
});
Task("Publish-GitHub")
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
.IsDependentOn("Package")
.Does(context =>
{
var apiKey = Argument<string>("github-key", null);
if(string.IsNullOrWhiteSpace(apiKey)) {
throw new CakeException("No GitHub API key was provided.");
}
// Publish to GitHub Packages
var exitCode = 0;
foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
{
context.Information("Publishing {0}...", file.GetFilename().FullPath);
exitCode += StartProcess("dotnet",
new ProcessSettings {
Arguments = new ProcessArgumentBuilder()
.Append("gpr")
.Append("push")
.AppendQuoted(file.FullPath)
.AppendSwitchSecret("-k", " ", apiKey)
}
);
}
if(exitCode != 0)
{
throw new CakeException("Could not push GitHub packages.");
}
});
Task("Publish-NuGet")
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
.IsDependentOn("Package")
@@ -152,7 +147,6 @@ Task("Publish-NuGet")
// Targets
Task("Publish")
.IsDependentOn("Publish-GitHub")
.IsDependentOn("Publish-NuGet");
Task("Default")

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<DefaultItemExcludes>$(DefaultItemExcludes);output\**;.gitignore</DefaultItemExcludes>
<NoWarn>MVC1000</NoWarn>

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.100",
"version": "8.0.100",
"rollForward": "latestFeature"
}
}

View File

@@ -1,3 +1,3 @@
{"version": 2, "width": 122, "height": 5, "title": "breakdown-chart (plain)", "env": {"TERM": "Spectre.Console"}}
{"version": 2, "width": 84, "height": 5, "title": "breakdown-chart (plain)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "\u001B[31m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[34m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[32m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[37m\u2588\u2588\u001B[0m\u001B[37m\u2588\u2588\u001B[0m\r\n\u001B[31m\u25A0\u001B[0m SCSS \u001B[37m80\u001B[0m \u001B[34m\u25A0\u001B[0m HTML \u001B[37m28.3\u001B[0m \u001B[32m\u25A0\u001B[0m C# \u001B[37m22.6\u001B[0m \u001B[37m\u25A0\u001B[0m JavaScript \u001B[37m6\u001B[0m \r\n\u001B[37m\u25A0\u001B[0m Ruby \u001B[37m6\u001B[0m \u001B[36m\u25A0\u001B[0m Shell \u001B[37m0.1\u001B[0m \r\n"]

View File

@@ -1,3 +1,3 @@
{"version": 2, "width": 122, "height": 5, "title": "breakdown-chart (rich)", "env": {"TERM": "Spectre.Console"}}
{"version": 2, "width": 84, "height": 5, "title": "breakdown-chart (rich)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "\u001B[38;5;9m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[38;5;12m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[38;5;2m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[38;5;11m\u2588\u2588\u001B[0m\u001B[38;5;119m\u2588\u2588\u001B[0m\r\n\u001B[38;5;9m\u25A0\u001B[0m SCSS \u001B[38;5;8m80\u001B[0m \u001B[38;5;12m\u25A0\u001B[0m HTML \u001B[38;5;8m28.3\u001B[0m \u001B[38;5;2m\u25A0\u001B[0m C# \u001B[38;5;8m22.6\u001B[0m \u001B[38;5;11m\u25A0\u001B[0m JavaScript \u001B[38;5;8m6\u001B[0m \r\n\u001B[38;5;119m\u25A0\u001B[0m Ruby \u001B[38;5;8m6\u001B[0m \u001B[38;5;14m\u25A0\u001B[0m Shell \u001B[38;5;8m0.1\u001B[0m \r\n"]

View File

@@ -0,0 +1,43 @@
{"version": 2, "width": 84, "height": 24, "title": "columns (plain)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "\u001B[?25l"]
[0.094, "o", "\u001B[1;37mApple\u001B[0m "]
[0.297, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m "]
[0.5, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m "]
[0.719, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m "]
[0.922, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m "]
[1.125, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m "]
[1.344, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m "]
[1.563, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m"]
[1.766, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m"]
[1.969, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m\r\n\u001B[1;37mCherry\u001B[0m "]
[2.172, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m\r\n\u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m "]
[2.375, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m\r\n\u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m "]
[2.594, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m "]
[2.797, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m "]
[3, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \u001B[1;37mGrape\u001B[0m "]
[3.219, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m"]
[3.422, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m\r\n\u001B[1;37mJackfruit\u001B[0m "]
[3.625, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m "]
[3.828, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m "]
[4.047, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m "]
[4.25, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m "]
[4.453, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m "]
[4.672, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m "]
[4.875, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m "]
[5.094, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m "]
[5.297, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m "]
[5.516, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m "]
[5.734, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m "]
[5.953, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m "]
[6.172, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m "]
[6.375, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m "]
[6.594, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m "]
[6.797, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m "]
[7.016, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m "]
[7.234, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m "]
[7.438, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m \r\n\u001B[1;37mXimenia\u001B[0m "]
[7.641, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m \r\n\u001B[1;37mXimenia\u001B[0m "]
[7.641, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K"]
[7.641, "o", "\u001B[?25h"]
[7.656, "o", "\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m \r\n\u001B[1;37mXimenia\u001B[0m \u001B[1;37mYuzu\u001B[0m \r\n"]

View File

@@ -0,0 +1,43 @@
{"version": 2, "width": 84, "height": 24, "title": "columns (rich)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "\u001B[?25l"]
[0, "o", "\u001B[1;38;5;11mApple\u001B[0m "]
[0.219, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m "]
[0.422, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m "]
[0.625, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m "]
[0.828, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m "]
[1.032, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m "]
[1.235, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m "]
[1.438, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m"]
[1.641, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m"]
[1.844, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m\r\n\u001B[1;38;5;11mCherry\u001B[0m "]
[2.047, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m\r\n\u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m "]
[2.266, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m\r\n\u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m "]
[2.485, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m "]
[2.703, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m "]
[2.907, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \u001B[1;38;5;11mGrape\u001B[0m "]
[3.11, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m"]
[3.313, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m\r\n\u001B[1;38;5;11mJackfruit\u001B[0m "]
[3.532, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m "]
[3.735, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m "]
[3.953, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m "]
[4.157, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m "]
[4.36, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m "]
[4.578, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m "]
[4.782, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m "]
[4.985, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m "]
[5.188, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m "]
[5.407, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m "]
[5.61, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m "]
[5.813, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m "]
[6.016, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m "]
[6.219, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m "]
[6.438, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m "]
[6.657, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m "]
[6.86, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m "]
[7.063, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m "]
[7.266, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m \r\n\u001B[1;38;5;11mXimenia\u001B[0m "]
[7.485, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m \r\n\u001B[1;38;5;11mXimenia\u001B[0m "]
[7.485, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K"]
[7.485, "o", "\u001B[?25h"]
[7.485, "o", "\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m \r\n\u001B[1;38;5;11mXimenia\u001B[0m \u001B[1;38;5;11mYuzu\u001B[0m \r\n"]

View File

@@ -1,3 +1,3 @@
{"version": 2, "width": 122, "height": 24, "title": "panel (plain)", "env": {"TERM": "Spectre.Console"}}
{"version": 2, "width": 84, "height": 24, "title": "panel (plain)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "\u2554\u2550\u2550\u001B[4mPasta Menu\u001B[0m\u2550\u2550\u2557\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u2551 \u001B[31mSpaghetti\u001B[0m \u2551\r\n\u2551 \u001B[31mLinguini\u001B[0m \u2551\r\n\u2551 \u001B[31mFettucine\u001B[0m \u2551\r\n\u2551 \u001B[31mTortellini\u001B[0m \u2551\r\n\u2551 \u001B[31mCapellini\u001B[0m \u2551\r\n\u2551 \u001B[31mLasagna\u001B[0m \u2551\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\r\n"]

View File

@@ -1,3 +1,3 @@
{"version": 2, "width": 122, "height": 24, "title": "panel (rich)", "env": {"TERM": "Spectre.Console"}}
{"version": 2, "width": 84, "height": 24, "title": "panel (rich)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "\u2554\u2550\u2550\u001B[4mPasta Menu\u001B[0m\u2550\u2550\u2557\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u2551 \u001B[38;5;9mSpaghetti\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mLinguini\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mFettucine\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mTortellini\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mCapellini\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mLasagna\u001B[0m \u2551\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\r\n"]

View File

@@ -0,0 +1,81 @@
Title: Spectre.Console 0.46 released!
Description: .NET 7 support, Layout Widget, JSON rendering
Published: 2023-01-10
Category: Release Notes
Excluded: false
---
Happy new year! 🎉
Version 0.46 of Spectre.Console has been released!
A lot has happened since the last release, but the most notable additions
and changes are support for [.NET 7](https://devblogs.microsoft.com/dotnet/announcing-dotnet-7/),
the new [Layout](https://spectreconsole.net/widgets/layout) widget, and
[rendering of JSON](https://spectreconsole.net/widgets/json). There has also been a lot of long overdue work
on the command line argument parser.
## New Contributors
* [@GaryMcD](https://github.com/GaryMcD) made their first contribution in [#961](https://github.com/spectreconsole/spectre.console/pull/961)
* [@eduherminio](https://github.com/eduherminio) made their first contribution in [#964](https://github.com/spectreconsole/spectre.console/pull/964)
* [@Saalvage](https://github.com/Saalvage) made their first contribution in [#976](https://github.com/spectreconsole/spectre.console/pull/976)
* [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) made their first contribution in [#1000](https://github.com/spectreconsole/spectre.console/pull/1000)
* [@nilaoda](https://github.com/nilaoda) made their first contribution in [#1012](https://github.com/spectreconsole/spectre.console/pull/1012)
* [@picture](https://github.com/picture)-vision made their first contribution in [#1013](https://github.com/spectreconsole/spectre.console/pull/1013)
* [@patrickfreilinger](https://github.com/patrickfreilinger) made their first contribution in [#1016](https://github.com/spectreconsole/spectre.console/pull/1016)
* [@sowa](https://github.com/sowa)705 made their first contribution in [#1014](https://github.com/spectreconsole/spectre.console/pull/1014)
* [@ardalis](https://github.com/ardalis) made their first contribution in [#1021](https://github.com/spectreconsole/spectre.console/pull/1021)
* [@Elisha](https://github.com/Elisha)-Aguilera made their first contribution in [#1038](https://github.com/spectreconsole/spectre.console/pull/1038)
* [@wguner](https://github.com/wguner) made their first contribution in [#1044](https://github.com/spectreconsole/spectre.console/pull/1044)
* [@bcwood](https://github.com/bcwood) made their first contribution in [#1068](https://github.com/spectreconsole/spectre.console/pull/1068)
* [@FrankRay](https://github.com/FrankRay)78 made their first contribution in [#1073](https://github.com/spectreconsole/spectre.console/pull/1073)
* [@tomkerkhove](https://github.com/tomkerkhove) made their first contribution in [#1089](https://github.com/spectreconsole/spectre.console/pull/1089)
* [@ArveSystad](https://github.com/ArveSystad) made their first contribution in [#1090](https://github.com/spectreconsole/spectre.console/pull/1090)
* [@maije](https://github.com/maije) made their first contribution in [#1096](https://github.com/spectreconsole/spectre.console/pull/1096)
* [@krisrok](https://github.com/krisrok) made their first contribution in [#953](https://github.com/spectreconsole/spectre.console/pull/953)
## What's changed?
* Add support for .NET 7.0 by [@patriksvensson](https://github.com/patriksvensson) in [#1056](https://github.com/spectreconsole/spectre.console/pull/1056)
* Add `Layout` widget by [@patriksvensson](https://github.com/patriksvensson) in [#1041](https://github.com/spectreconsole/spectre.console/pull/1041)
* Add JSON text renderer by [@patriksvensson](https://github.com/patriksvensson) in [#1086](https://github.com/spectreconsole/spectre.console/pull/1086)
* Backward direction of text prompt autocomplete by [@nkochnev](https://github.com/nkochnev) in [#921](https://github.com/spectreconsole/spectre.console/pull/921)
* Custom mask for secret by [@GaryMcD](https://github.com/GaryMcD) in [#970](https://github.com/spectreconsole/spectre.console/pull/970)
* Allow selections to wrap around by [@Saalvage](https://github.com/Saalvage) in [#976](https://github.com/spectreconsole/spectre.console/pull/976)
* Join .NET Foundation by [@patriksvensson](https://github.com/patriksvensson) in [#978](https://github.com/spectreconsole/spectre.console/pull/978)
* Adding value: a single semi-colon! by [@johanlindfors](https://github.com/johanlindfors) in [#986](https://github.com/spectreconsole/spectre.console/pull/986)
* Fix `@` being used in Figlet font by [@Saalvage](https://github.com/Saalvage) in [#972](https://github.com/spectreconsole/spectre.console/pull/972)
* Add new and transferred issues to backlog project by [@patriksvensson](https://github.com/patriksvensson) in [#995](https://github.com/spectreconsole/spectre.console/pull/995)
* Pin SDK due to a bug in .NET 6.0.401 by [@patriksvensson](https://github.com/patriksvensson) in [#1011](https://github.com/spectreconsole/spectre.console/pull/1011)
* Remove period trimming by [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) in [#1008](https://github.com/spectreconsole/spectre.console/pull/1008)
* Allow `PACKET` key on MultiSelectionPrompt by [@nilaoda](https://github.com/nilaoda) in [#1012](https://github.com/spectreconsole/spectre.console/pull/1012)
* Added Suckless Simple Terminal to list of ANSI terminals by [@picture](https://github.com/picture)-vision in [#1013](https://github.com/spectreconsole/spectre.console/pull/1013)
* Add culture option to `TypeConverterHelper`, `TextPrompt` and `AnsiConsole` by [@sowa](https://github.com/sowa)705 in [#1014](https://github.com/spectreconsole/spectre.console/pull/1014)
* Minor typo fixes by [@ardalis](https://github.com/ardalis) in [#1021](https://github.com/spectreconsole/spectre.console/pull/1021)
* Alignment fixes by [@patriksvensson](https://github.com/patriksvensson) in [#1066](https://github.com/spectreconsole/spectre.console/pull/1066)
* `IndexOf` replaced by Count at Add method - Performance issue #975 fixed by [@maije](https://github.com/maije) in [#1096](https://github.com/spectreconsole/spectre.console/pull/1096)
* Modified tokenizer not to break on on `]]]` at the end of a style by [@nils](https://github.com/nils)-a in [#1027](https://github.com/spectreconsole/spectre.console/pull/1027)
* Command line argument parsing improvements by [@FrankRay](https://github.com/FrankRay)78 in [#1048](https://github.com/spectreconsole/spectre.console/pull/1048)
* Show help for default command by [@krisrok](https://github.com/krisrok) in [#953](https://github.com/spectreconsole/spectre.console/pull/953)
* Automatically display default values of options in the help page by @0xced in [#1032](https://github.com/spectreconsole/spectre.console/pull/1032)
## Documentation updates
* Add link to documentation in README by [@ardalis](https://github.com/ardalis) in [#1030](https://github.com/spectreconsole/spectre.console/pull/1030)
* Update `.NET 5` references in docs by [@eduherminio](https://github.com/eduherminio) in [#964](https://github.com/spectreconsole/spectre.console/pull/964)
* Blog date fix by [@phil](https://github.com/phil)-scott-78 in [#963](https://github.com/spectreconsole/spectre.console/pull/963)
* Update sponsors by [@tomkerkhove](https://github.com/tomkerkhove) in [#1089](https://github.com/spectreconsole/spectre.console/pull/1089)
* Inline `CommandArgument` required/optional style in template parameter docs by [@ArveSystad](https://github.com/ArveSystad) in [#1090](https://github.com/spectreconsole/spectre.console/pull/1090)
* Add documentation for `BreakdownChart` by [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) in [#1000](https://github.com/spectreconsole/spectre.console/pull/1000)
* Create `Panel` documentation by [@patrickfreilinger](https://github.com/patrickfreilinger) in [#1016](https://github.com/spectreconsole/spectre.console/pull/1016)
* Added details for using links within markup. by [@GaryMcD](https://github.com/GaryMcD) in [#961](https://github.com/spectreconsole/spectre.console/pull/961)
* Added documentation for `Rows` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1038](https://github.com/spectreconsole/spectre.console/pull/1038)
* Added documentation guide for `Grid` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1043](https://github.com/spectreconsole/spectre.console/pull/1043)
* Added documentation guide for the `Padder` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1046](https://github.com/spectreconsole/spectre.console/pull/1046)
* Created a `Columns` widget documentation by [@wguner](https://github.com/wguner) in [#1044](https://github.com/spectreconsole/spectre.console/pull/1044)
* Fixed typo in `Panel` documentation [@bcwood](https://github.com/bcwood) in [#1068](https://github.com/spectreconsole/spectre.console/pull/1068)
* Clarified the license for `SixLabors.ImageSharp` by [@FrankRay](https://github.com/FrankRay)78 in [#1073](https://github.com/spectreconsole/spectre.console/pull/1073)
* Add documentation for `Layout` by [@patriksvensson](https://github.com/patriksvensson) in [#1127](https://github.com/spectreconsole/spectre.console/pull/1127)
## Dependencies
* Update dependency `Wcwidth.Sources` to `v1` by [@renovate](https://github.com/renovate) in [#969](https://github.com/spectreconsole/spectre.console/pull/969)
* Update `actions/setup-dotnet` action to `v3` by [@renovate](https://github.com/renovate) in [#982](https://github.com/spectreconsole/spectre.console/pull/982)
* Update dependency `Microsoft.NET.Test.Sdk` to `v17.3.2` by [@renovate](https://github.com/renovate) in [#977](https://github.com/spectreconsole/spectre.console/pull/977)
* Update dependency `cake.tool` to `v2.3.0` by [@renovate](https://github.com/renovate) in [#1015](https://github.com/spectreconsole/spectre.console/pull/1015)

View File

@@ -0,0 +1,48 @@
Title: Spectre.Console 0.47 released!
Description: Alacritty terminal support, command line improvements
Published: 2023-05-19
Category: Release Notes
Excluded: false
---
Version 0.47 of Spectre.Console has been released!
There are a lot of fixes and improvements in this release, the most noteworthy changes being support for the [Alacritty](https://github.com/alacritty/alacritty) terminal and continued improvements to command line parsing.
Thank you to all contributers.
## New Contributors
* [@wbaldoumas](https://github.com/wbaldoumas) made their first contribution in [#1143](https://github.com/spectreconsole/spectre.console/pull/1143)
* [@MartinZikmund](https://github.com/MartinZikmund) made their first contribution in [#1151](https://github.com/spectreconsole/spectre.console/pull/1151)
* [@ilyahryapko](https://github.com/ilyahryapko) made their first contribution in [#1131](https://github.com/spectreconsole/spectre.console/pull/1131)
* [@meziantou](https://github.com/meziantou) made their first contribution in [#1174](https://github.com/spectreconsole/spectre.console/pull/1174)
* [@MaxAtoms](https://github.com/MaxAtoms) made their first contribution in [#1211](https://github.com/spectreconsole/spectre.console/pull/1211)
* [@phillip-haydon](https://github.com/phillip-haydon) made their first contribution in [#1218](https://github.com/spectreconsole/spectre.console/pull/1218)
## What's Changed
* Add Alacritty to the supported terminals in AnsiDetector by [@MaxAtoms](https://github.com/MaxAtoms) in [#1211](https://github.com/spectreconsole/spectre.console/pull/1211)
* Add an implicit operator to convert from Color to Style by [@0xced](https://github.com/0xced) in [#1160](https://github.com/spectreconsole/spectre.console/pull/1160)
* Allow case-insensitive confirmation prompt by [@MartinZikmund](https://github.com/MartinZikmund) in [#1151](https://github.com/spectreconsole/spectre.console/pull/1151)
* Allow configuration of confirmation prompt comparison via `StringComparer` by [@MartinZikmund](https://github.com/MartinZikmund) in [#1161](https://github.com/spectreconsole/spectre.console/pull/1161)
* Do not register analyzer if SpectreConsole is not available in the current compilation by [@meziantou](https://github.com/meziantou) in [#1172](https://github.com/spectreconsole/spectre.console/pull/1172)
* Ensure correct comparer is used for `TextPrompt` by [@MartinZikmund](https://github.com/MartinZikmund) in [#1152](https://github.com/spectreconsole/spectre.console/pull/1152)
* Forward CancellationToken to GetOperation by [@meziantou](https://github.com/meziantou) in [#1173](https://github.com/spectreconsole/spectre.console/pull/1173)
* Fix minor typo in Prompt example by [@Frassle](https://github.com/Frassle) in [#1183](https://github.com/spectreconsole/spectre.console/pull/1183)
* Fix coconut spelling by [@phillip-haydon](https://github.com/phillip-haydon) in [#1218](https://github.com/spectreconsole/spectre.console/pull/1218)
* Improve conversion error messages by [@0xced](https://github.com/0xced) in [#1141](https://github.com/spectreconsole/spectre.console/pull/1141)
* Make the code fix more robust and detect more symbols of type IAnsiConsole by [@meziantou](https://github.com/meziantou) in [#1169](https://github.com/spectreconsole/spectre.console/pull/1169)
* Minor Refactorings by [@Elisha-Aguilera](https://github.com/Elisha-Aguilera) in [#1081](https://github.com/spectreconsole/spectre.console/pull/1081)
* Simplify access to the SemanticModel in analyzers by [@meziantou](https://github.com/meziantou) in [#1167](https://github.com/spectreconsole/spectre.console/pull/1167)
* Use SymbolEqualityComparer.Default when possible by [@meziantou](https://github.com/meziantou) in [#1171](https://github.com/spectreconsole/spectre.console/pull/1171)
* Use StringComparison.Ordinal instead of culture-sensitive comparisons by [@meziantou](https://github.com/meziantou) in [#1174](https://github.com/spectreconsole/spectre.console/pull/1174)
## Command line updates
* Add possibility to set description and/or data for the default command by [@0xced](https://github.com/0xced) in [#1091](https://github.com/spectreconsole/spectre.console/pull/1091)
* Add support for converting command parameters into FileInfo and DirectoryInfo by [@0xced](https://github.com/0xced) in [#1145](https://github.com/spectreconsole/spectre.console/pull/1145)
* Add support for arrays in \[DefaultValue\] attributes by [@0xced](https://github.com/0xced) in [#1164](https://github.com/spectreconsole/spectre.console/pull/1164)
* Add ability to pass example args using `params` syntax by [@seclerp](https://github.com/seclerp) in [#1166](https://github.com/spectreconsole/spectre.console/pull/1166)
* Alias for branches by [@ilyahryapko](https://github.com/ilyahryapko) in [#1131](https://github.com/spectreconsole/spectre.console/pull/1131)
* Command line improvements by [@FrankRay78](https://github.com/FrankRay78) in [#1103](https://github.com/spectreconsole/spectre.console/pull/1103)
## Documentation updates
* Alignment => Justification Docs Fixes by [@wbaldoumas](https://github.com/wbaldoumas) in [#1143](https://github.com/spectreconsole/spectre.console/pull/1143)

View File

@@ -0,0 +1,47 @@
Title: Command Help
Order: 13
Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help."
---
Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line.
The automatically generated help is derived from the configured commands and their command settings.
The help is also context aware and tailored depending on what has been specified on the command line before it. For example,
1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.
2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.
`HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`.
## Custom help providers
Whilst it shouldn't be common place to implement your own help provider, it is however possible.
You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.
```csharp
using Spectre.Console.Cli;
namespace Help;
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<DefaultCommand>();
app.Configure(config =>
{
// Register the custom help provider
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
});
return app.Run(args);
}
}
```
There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this.

View File

@@ -43,7 +43,7 @@ For more complex command hierarchical configurations, they can also be composed
## Customizing Command Configurations
The `Configure` method is also used to change how help for the commands is generated. This configuration will give our command an additional alias of `file-size` and a description to be used when displaying the help. Additional, an example is specified that will be parsed and displayed for users asking for help. Multiple examples can be provided. Commands can also be marked as hidden. With this option they are still executable, but will not be displayed in help screens.
The `Configure` method is also used to change how help for the commands is generated. This configuration will give our command an additional alias of `file-size` and a description to be used when displaying the help. Additionally, an example is specified that will be parsed and displayed for users asking for help. Multiple examples can be provided. Commands can also be marked as hidden. With this option they are still executable, but will not be displayed in help screens.
``` csharp
var app = new CommandApp();

View File

@@ -37,8 +37,8 @@ app.Configure(config =>
config.AddCommand<HelloCommand>("hello")
.WithAlias("hola")
.WithDescription("Say hello")
.WithExample(new []{"hello", "Phil"})
.WithExample(new []{"hello", "Phil", "--count", "4"});
.WithExample("hello", "Phil")
.WithExample("hello", "Phil", "--count", "4");
});
```

View File

@@ -49,11 +49,11 @@ namespace MyApp
## Using a custom ExceptionHandler
Using the `SetErrorHandler()` during configuration it is possible to handle exceptions in a defined way.
Using the `SetExceptionHandler()` during configuration it is possible to handle exceptions in a defined way.
This method comes in two flavours: One that uses the default exitCode (or `return` value) of `-1` and one
where the exitCode needs to be supplied.
### Using `SetErrorHandler(Func<Exception, int> handler)`
### Using `SetExceptionHandler(Func<Exception, int> handler)`
Using this method exceptions can be handled in a custom way. The return value of the handler is used as
the exitCode for the application.
@@ -84,9 +84,9 @@ namespace MyApp
}
```
### Using `SetErrorHandler(Action<Exception> handler)`
### Using `SetExceptionHandler(Action<Exception> handler)`
Using this method exceptions can be handled in a custom way, much the same as with the `SetErrorHandler(Func<Exception, int> handler)`.
Using this method exceptions can be handled in a custom way, much the same as with the `SetExceptionHandler(Func<Exception, int> handler)`.
Using the `Action` as the handler however, it is not possible (or required) to supply a return value.
The exitCode for the application will be `-1`.

View File

@@ -26,7 +26,7 @@ This setting file tells `Spectre.Console.Cli` that our command has two parameter
## CommandArgument
Arguments have a position and a name. The name is not only used for generating help, but its formatting is used to determine whether or not the argument is optional. The name must either be surrounded by square brackets (e.g. `[name]`) or angle brackets (e.g. `<name>`). Angle brackets denote required whereas square brackets denote optional. If neither are specified an exception will be thrown.
Arguments have a position and a name. The name is not only used for generating help, but its formatting is used to determine whether or not the argument is optional. Angle brackets denote a required argument (e.g. `<name>`) whereas square brackets denote an optional argument (e.g. `[name]`). If neither are specified an exception will be thrown.
The position is used for scenarios where there could be more than one argument.
@@ -86,7 +86,9 @@ public int Count { get; set; }
## Arrays
`CommandArgument` can be defined as arrays and any additional parameters will be included in the value. For example
### Argument Vector
One (exactly one) `CommandArgument` can be defined as an array, and any additional parameters will be included in the value. For example:
```csharp
[CommandArgument(0, "[name]")]
@@ -95,6 +97,19 @@ public string[] Name { get; set; }
Would allow the user to run `app.exe Dwayne Elizondo "Mountain Dew" Herbert Camacho`. The settings passed to the command would have a 5 element array consisting of Dwayne, Elizondo, Mountain Dew, Herbert and Camacho.
A command can have only one argument vector, and it needs to be the last argument. (I.e. there can be no `CommandArgument` whose position is higher than that of the argument vector.)
### Option Arrays
A `CommandOption` can be defined as an array like the following:
```csharp
[CommandOption("-n|--name <VALUES>")]
public string[] Names { get; set; },
```
This would allow the user to run `app.exe --name Dwayne --name Elizondo --name "Mountain Dew" --name Herbert --name Camacho` and would result in a 5 element array consisting of Dwayne, Elizondo, Mountain Dew, Herbert and Camacho.
## Constructors
`Spectre.Console.Cli` supports constructor initialization and init only initialization. For constructor initialization, the parameter name of the constructor must match the name of the property name of the settings class. Order does not matter.

View File

@@ -31,7 +31,7 @@ var fruits = AnsiConsole.Prompt(
.AddChoices(new[] {
"Apple", "Apricot", "Avocado",
"Banana", "Blackcurrant", "Blueberry",
"Cherry", "Cloudberry", "Cocunut",
"Cherry", "Cloudberry", "Coconut",
}));
// Write the selected fruits to the terminal

View File

@@ -12,7 +12,7 @@ Spectre.Console can render [FIGlet](http://www.figlet.org/) text by using the `F
```csharp
AnsiConsole.Write(
new FigletText("Hello")
.LeftAligned()
.LeftJustified()
.Color(Color.Red));
```
@@ -26,6 +26,6 @@ var font = FigletFont.Load("starwars.flf");
AnsiConsole.Write(
new FigletText(font, "Hello")
.LeftAligned()
.LeftJustified()
.Color(Color.Red));
```

View File

@@ -45,16 +45,16 @@ grid.AddColumn();
// Add header row
grid.AddRow(new Text[]{
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftAligned(),
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftJustified(),
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightAligned()
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightJustified()
});
// Add content row
grid.AddRow(new Text[]{
new Text("Row 1").LeftAligned(),
new Text("Row 1").LeftJustified(),
new Text("Row 2").Centered(),
new Text("Row 3").RightAligned()
new Text("Row 3").RightJustified()
});
// Write centered cell grid contents to Console
@@ -73,9 +73,9 @@ grid.AddColumn();
// Add header row
grid.AddRow(new Text[]{
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftAligned(),
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftJustified(),
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightAligned()
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightJustified()
});
var embedded = new Grid();
@@ -88,7 +88,7 @@ embedded.AddRow(new Text("Embedded III"), new Text("Embedded IV"));
// Add content row
grid.AddRow(
new Text("Row 1").LeftAligned(),
new Text("Row 1").LeftJustified(),
new Text("Row 2").Centered(),
embedded
);

View File

@@ -41,7 +41,7 @@ You can set the rule's title alignment.
```csharp
var rule = new Rule("[red]Hello[/]");
rule.Alignment = Justify.Left;
rule.Justification = Justify.Left;
AnsiConsole.Write(rule);
```
@@ -53,7 +53,7 @@ You can also specify it via an extension method:
```csharp
var rule = new Rule("[red]Hello[/]");
rule.LeftAligned();
rule.LeftJustified();
AnsiConsole.Write(rule);
```

View File

@@ -34,7 +34,7 @@ You can also specify styles via extension methods:
```csharp
var path = new TextPath("C:/This/Path/Is/Too/Long/To/Fit/In/The/Area.txt")
.RightAligned();
.RightJustified();
```
## Styling

View File

@@ -3,17 +3,11 @@
"isRoot": true,
"tools": {
"cake.tool": {
"version": "3.0.0",
"version": "4.0.0",
"commands": [
"dotnet-cake"
]
},
"gpr": {
"version": "0.1.281",
"commands": [
"gpr"
]
},
"dotnet-example": {
"version": "2.0.0",
"commands": [

View File

@@ -9,7 +9,7 @@ public static partial class Program
{
[CommandOption("--count")]
[Description("The number of bars to print")]
[DefaultValue(1)]
[DefaultValue(3)]
public int Count { get; set; }
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Delegates</ExampleName>
<ExampleDescription>Demonstrates how to specify commands as delegates.</ExampleDescription>

View File

@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Spectre.Console;
using Spectre.Console.Cli;
@@ -14,7 +15,13 @@ public static partial class Program
.WithDescription("Foos the bars");
config.AddDelegate<BarSettings>("bar", Bar)
.WithDescription("Bars the foos"); ;
.WithDescription("Bars the foos");
config.AddAsyncDelegate("fooAsync", FooAsync)
.WithDescription("Foos the bars asynchronously");
config.AddAsyncDelegate<BarSettings>("barAsync", BarAsync)
.WithDescription("Bars the foos asynchronously");
});
return app.Run(args);
@@ -35,4 +42,20 @@ public static partial class Program
return 0;
}
private static Task<int> FooAsync(CommandContext context)
{
AnsiConsole.WriteLine("Foo");
return Task.FromResult(0);
}
private static Task<int> BarAsync(CommandContext context, BarSettings settings)
{
for (var index = 0; index < settings.Count; index++)
{
AnsiConsole.WriteLine("Bar");
}
return Task.FromResult(0);
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Demo</ExampleName>
<ExampleDescription>Demonstrates the most common use cases of Spectre.Cli.</ExampleDescription>

View File

@@ -15,7 +15,7 @@ public static class Program
{
config.SetApplicationName("fake-dotnet");
config.ValidateExamples();
config.AddExample(new[] { "run", "--no-build" });
config.AddExample("run", "--no-build");
// Run
config.AddCommand<RunCommand>("run");
@@ -30,8 +30,8 @@ public static class Program
// Serve
config.AddCommand<ServeCommand>("serve")
.WithExample(new[] { "serve", "-o", "firefox" })
.WithExample(new[] { "serve", "--port", "80", "-o", "firefox" });
.WithExample("serve", "-o", "firefox")
.WithExample("serve", "--port", "80", "-o", "firefox");
});
return app.Run(args);

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Dynamic</ExampleName>
<ExampleDescription>Demonstrates how to define dynamic commands.</ExampleDescription>

View File

@@ -0,0 +1,30 @@
using System.Linq;
using Spectre.Console;
using Spectre.Console.Cli;
using Spectre.Console.Cli.Help;
using Spectre.Console.Rendering;
namespace Help;
/// <summary>
/// Example showing how to extend the built-in Spectre.Console help provider
/// by rendering a custom banner at the top of the help information
/// </summary>
internal class CustomHelpProvider : HelpProvider
{
public CustomHelpProvider(ICommandAppSettings settings)
: base(settings)
{
}
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
{
return new[]
{
new Text("--------------------------------------"), Text.NewLine,
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
new Text("--------------------------------------"), Text.NewLine,
Text.NewLine,
};
}
}

View File

@@ -0,0 +1,20 @@
using Spectre.Console;
using Spectre.Console.Cli;
namespace Help;
public sealed class DefaultCommand : Command
{
private IAnsiConsole _console;
public DefaultCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context)
{
_console.WriteLine("Hello world");
return 0;
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ExampleName>Help</ExampleName>
<ExampleDescription>Demonstrates how to extend the built-in Spectre.Console help provider to render a custom banner at the top of the help information.</ExampleDescription>
<ExampleGroup>Cli</ExampleGroup>
<ExampleVisible>false</ExampleVisible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,19 @@
using Spectre.Console.Cli;
namespace Help;
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<DefaultCommand>();
app.Configure(config =>
{
// Register the custom help provider
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
});
return app.Run(args);
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Injection</ExampleName>
<ExampleDescription>Demonstrates how to use dependency injection with Spectre.Cli.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Logging</ExampleName>
<ExampleDescription>Demonstrates how to dynamically configure Serilog for logging using parameters from a command.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Screens</ExampleTitle>
<ExampleDescription>Demonstrates how to use alternate screens.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Borders</ExampleTitle>
<ExampleDescription>Demonstrates the different kind of borders.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -20,7 +20,8 @@ public static class Program
{
static IRenderable CreatePanel(string name, BoxBorder border)
{
return new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
return
new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
.Header($" [blue]{name}[/] ", Justify.Center)
.Border(border)
.BorderStyle(Style.Parse("grey"));
@@ -47,6 +48,7 @@ public static class Program
static IRenderable CreateTable(string name, TableBorder border)
{
var table = new Table().Border(border);
table.ShowRowSeparators();
table.AddColumn("[yellow]Header 1[/]", c => c.Footer("[grey]Footer 1[/]"));
table.AddColumn("[yellow]Header 2[/]", col => col.Footer("[grey]Footer 2[/]").RightAligned());
table.AddRow("Cell", "Cell");
@@ -54,28 +56,22 @@ public static class Program
return new Panel(table)
.Header($" [blue]{name}[/] ", Justify.Center)
.PadBottom(1)
.NoBorder();
}
var items = new[]
{
CreateTable("Ascii", TableBorder.Ascii),
CreateTable("Ascii2", TableBorder.Ascii2),
CreateTable("Ascii", TableBorder.Ascii), CreateTable("Ascii2", TableBorder.Ascii2),
CreateTable("AsciiDoubleHead", TableBorder.AsciiDoubleHead),
CreateTable("Horizontal", TableBorder.Horizontal),
CreateTable("Simple", TableBorder.Simple),
CreateTable("SimpleHeavy", TableBorder.SimpleHeavy),
CreateTable("Minimal", TableBorder.Minimal),
CreateTable("Horizontal", TableBorder.Horizontal), CreateTable("Simple", TableBorder.Simple),
CreateTable("SimpleHeavy", TableBorder.SimpleHeavy), CreateTable("Minimal", TableBorder.Minimal),
CreateTable("MinimalHeavyHead", TableBorder.MinimalHeavyHead),
CreateTable("MinimalDoubleHead", TableBorder.MinimalDoubleHead),
CreateTable("Square", TableBorder.Square),
CreateTable("Rounded", TableBorder.Rounded),
CreateTable("Heavy", TableBorder.Heavy),
CreateTable("HeavyEdge", TableBorder.HeavyEdge),
CreateTable("HeavyHead", TableBorder.HeavyHead),
CreateTable("Double", TableBorder.Double),
CreateTable("DoubleEdge", TableBorder.DoubleEdge),
CreateTable("Markdown", TableBorder.Markdown),
CreateTable("Square", TableBorder.Square), CreateTable("Rounded", TableBorder.Rounded),
CreateTable("Heavy", TableBorder.Heavy), CreateTable("HeavyEdge", TableBorder.HeavyEdge),
CreateTable("HeavyHead", TableBorder.HeavyHead), CreateTable("Double", TableBorder.Double),
CreateTable("DoubleEdge", TableBorder.DoubleEdge), CreateTable("Markdown", TableBorder.Markdown),
};
AnsiConsole.Write(new Columns(items).Collapse());

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Calendars</ExampleTitle>
<ExampleDescription>Demonstrates how to render calendars.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Canvas</ExampleTitle>
<ExampleDescription>Demonstrates how to render pixels and images.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Charts</ExampleTitle>
<ExampleDescription>Demonstrates how to render charts in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -23,6 +23,7 @@ public static class Program
.FullSize()
.Width(60)
.ShowPercentage()
.WithValueColor(Color.Orange1)
.AddItem("SCSS", 37, Color.Red)
.AddItem("HTML", 28.3, Color.Blue)
.AddItem("C#", 22.6, Color.Green)

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Colors</ExampleTitle>
<ExampleDescription>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Columns</ExampleTitle>
<ExampleDescription>Demonstrates how to render data into columns.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Cursor</ExampleTitle>
<ExampleDescription>Demonstrates how to move the cursor.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Decorations</ExampleTitle>
<ExampleDescription>Demonstrates how to [italic]use[/] [bold]decorations[/] [dim]in[/] the console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
using Spectre.Console;
namespace Colors;
public static class Program
{
public static void Main()
{
AnsiConsole.ResetDecoration();
AnsiConsole.WriteLine();
if (AnsiConsole.Profile.Capabilities.Ansi)
{
AnsiConsole.Write(new Rule("[bold green]ANSI Decorations[/]"));
}
else
{
AnsiConsole.Write(new Rule("[bold red]Legacy Decorations (unsupported)[/]"));
}
var decorations = System.Enum.GetValues(typeof(Decoration));
foreach (var decoration in decorations)
{
var name = System.Enum.GetName(typeof(Decoration),decoration);
AnsiConsole.Write(name + ": ");
AnsiConsole.Write(new Markup(name+"\n", new Style(decoration: (Decoration)decoration)));
}
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Emojis</ExampleTitle>
<ExampleDescription>Demonstrates how to render emojis.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Exceptions</ExampleTitle>
<ExampleDescription>Demonstrates how to render formatted exceptions.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Figlet</ExampleTitle>
<ExampleDescription>Demonstrates how to render FIGlet text.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Grids</ExampleTitle>
<ExampleDescription>Demonstrates how to render grids in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Info</ExampleTitle>
<ExampleDescription>Displays the capabilities of the current console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,15 +2,15 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Json</ExampleTitle>
<ExampleDescription>Demonstrates how to print syntax highlighted JSON.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
<ProjectReference Include="..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" />
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Layout</ExampleTitle>
<ExampleDescription>Demonstrates how to use layouts.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Links</ExampleTitle>
<ExampleDescription>Demonstrates how to render links in a console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Live</ExampleTitle>
<ExampleDescription>Demonstrates how to do live updates.</ExampleDescription>
<ExampleGroup>Live</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>LiveTable</ExampleTitle>
<ExampleDescription>Demonstrates how to do live updates in a table.</ExampleDescription>
<ExampleGroup>Live</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<ExampleTitle>Minimal</ExampleTitle>
<ExampleDescription>Demonstrates a minimal console application.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Panels</ExampleTitle>
<ExampleDescription>Demonstrates how to render items in panels.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Paths</ExampleTitle>
<ExampleDescription>Demonstrates how to render paths.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Spectre.Console;
using Spectre.Console.Rendering;
namespace Progress;
@@ -22,6 +24,7 @@ public static class Program
new RemainingTimeColumn(), // Remaining time
new SpinnerColumn(), // Spinner
})
.UseRenderHook((renderable, tasks) => RenderHook(tasks, renderable))
.Start(ctx =>
{
var random = new Random(DateTime.Now.Millisecond);
@@ -65,6 +68,35 @@ public static class Program
AnsiConsole.MarkupLine("[green]Done![/]");
}
private static IRenderable RenderHook(IReadOnlyList<ProgressTask> tasks, IRenderable renderable)
{
var header = new Panel("Going on a :rocket:, we're going to the :crescent_moon:").Expand().RoundedBorder();
var footer = new Rows(
new Rule(),
new Markup(
$"[blue]{tasks.Count}[/] total tasks. [green]{tasks.Count(i => i.IsFinished)}[/] complete.")
);
const string ESC = "\u001b";
string escapeSequence;
if (tasks.All(i => i.IsFinished))
{
escapeSequence = $"{ESC}]]9;4;0;100{ESC}\\";
}
else
{
var total = tasks.Sum(i => i.MaxValue);
var done = tasks.Sum(i => i.Value);
var percent = (int)(done / total * 100);
escapeSequence = $"{ESC}]]9;4;1;{percent}{ESC}\\";
}
var middleContent = new Grid().AddColumns(new GridColumn(), new GridColumn().Width(20));
middleContent.AddRow(renderable, new FigletText(tasks.Count(i => i.IsFinished == false).ToString()));
return new Rows(header, middleContent, footer, new ControlCode(escapeSequence));
}
private static List<(ProgressTask Task, int Delay)> CreateTasks(ProgressContext progress, Random random)
{
var tasks = new List<(ProgressTask, int)>();

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Progress</ExampleTitle>
<ExampleDescription>Demonstrates how to show progress bars.</ExampleDescription>
<ExampleGroup>Status</ExampleGroup>

View File

@@ -115,7 +115,7 @@ namespace Prompt
.AddChoices(favorites));
}
AnsiConsole.MarkupLine("Your selected: [yellow]{0}[/]", fruit);
AnsiConsole.MarkupLine("You selected: [yellow]{0}[/]", fruit);
return fruit;
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>9</LangVersion>
<ExampleTitle>Prompt</ExampleTitle>
<ExampleDescription>Demonstrates how to get input from a user.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Rules</ExampleTitle>
<ExampleDescription>Demonstrates how to render horizontal rules (lines).</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,9 +2,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Showcase</ExampleTitle>
<ExampleDescription>Demonstation of Spectre.Console.</ExampleDescription>
<ExampleDescription>Demonstration of Spectre.Console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>
</PropertyGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Status</ExampleTitle>
<ExampleDescription>Demonstrates how to show status updates.</ExampleDescription>
<ExampleGroup>Status</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Tables</ExampleTitle>
<ExampleDescription>Demonstrates how to render tables in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleTitle>Trees</ExampleTitle>
<ExampleDescription>Demonstrates how to render trees in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -81,6 +81,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\La
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Json", "Console\Json\Json.csproj", "{ABE3E734-0756-4D5A-B28A-E6E526D9927D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "..\src\Spectre.Console.Json\Spectre.Console.Json.csproj", "{91A5637F-1F89-48B3-A0BA-6CC629807393}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Help", "Cli\Help\Help.csproj", "{BAB490D6-FF8D-462B-B2B0-933384D629DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Decorations", "Console\Decorations\Decorations.csproj", "{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -535,6 +541,42 @@ Global
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x64.Build.0 = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x86.ActiveCfg = Release|Any CPU
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x86.Build.0 = Release|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Debug|x64.ActiveCfg = Debug|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Debug|x64.Build.0 = Debug|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Debug|x86.ActiveCfg = Debug|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Debug|x86.Build.0 = Debug|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|Any CPU.Build.0 = Release|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x64.ActiveCfg = Release|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x64.Build.0 = Release|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x86.ActiveCfg = Release|Any CPU
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x86.Build.0 = Release|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x64.ActiveCfg = Debug|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x64.Build.0 = Debug|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x86.ActiveCfg = Debug|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x86.Build.0 = Debug|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|Any CPU.Build.0 = Release|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x64.ActiveCfg = Release|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x64.Build.0 = Release|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x86.ActiveCfg = Release|Any CPU
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x86.Build.0 = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Debug|x64.ActiveCfg = Debug|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Debug|x64.Build.0 = Debug|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Debug|x86.ActiveCfg = Debug|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Debug|x86.Build.0 = Debug|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|Any CPU.Build.0 = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x64.ActiveCfg = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x64.Build.0 = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x86.ActiveCfg = Release|Any CPU
{FC5852F1-E01F-4DF7-9B49-CA19A9EE670F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -549,6 +591,8 @@ Global
{0C58FB17-F60A-47AB-84BF-961EC8C06AE6} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
{91A5637F-1F89-48B3-A0BA-6CC629807393} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
{BAB490D6-FF8D-462B-B2B0-933384D629DB} = {4682E9B7-B54C-419D-B92F-470DA4E5674C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3EE724C5-CAB4-410D-AC63-8D4260EF83ED}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ExampleVisible>false</ExampleVisible>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@@ -1,5 +1,7 @@
{
"$schema": "http://json.schemastore.org/global",
"sdk": {
"version": "7.0.100"
"version": "8.0.100",
"rollForward": "latestFeature"
}
}

View File

@@ -9,7 +9,7 @@ namespace Generator.Commands.Samples
{
public abstract void Run(IAnsiConsole console);
public virtual string Name() => PascalToKebab(GetType().Name.Replace("Sample",""));
public virtual (int Cols, int Rows) ConsoleSize => (120, 24);
public virtual (int Cols, int Rows) ConsoleSize => (82, 24);
public virtual IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
{
return new (string Name, Action<Capabilities> CapabilitiesAction)[]

View File

@@ -0,0 +1,94 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Spectre.Console;
// ReSharper disable once CheckNamespace
namespace Generator.Commands.Samples;
// ReSharper disable once UnusedType.Global
public class ColumnsSample : BaseSample
{
public override void Run(IAnsiConsole console)
{
var cards = Fruit
.LoadFriuts()
.Select(GetContent)
.ToList();
// Animate
console.Live(new Text(""))
.AutoClear(true)
.Overflow(VerticalOverflow.Ellipsis)
.Cropping(VerticalOverflowCropping.Top)
.Start(ctx =>
{
for (var i = 1; i < cards.Count; i++)
{
var toShow = cards.Take(i);
ctx.UpdateTarget(new Columns(toShow));
//ctx.Refresh();
Thread.Sleep(200);
}
});
// Render all cards in columns
AnsiConsole.Write(new Spectre.Console.Columns(cards));
}
private static string GetContent(Fruit fruit)
{
return $"[b][yellow]{fruit.Name}[/][/]";
}
private sealed class Fruit
{
public string Name { get; init; }
public static List<Fruit> LoadFriuts()
{
return new []
{
"Apple",
"Apricot",
"Avocado",
"Banana",
"Blackberry",
"Blueberry",
"Boysenberry",
"Breadfruit",
"Cacao",
"Cherry",
"Cloudberry",
"Coconut",
"Dragonfruit",
"Elderberry",
"Grape",
"Grapefruit",
"Jackfruit",
"Kiwifruit",
"Lemon",
"Lime",
"Mango",
"Melon",
"Orange",
"Blood orange",
"Clementine",
"Mandarine",
"Tangerine",
"Papaya",
"Passionfruit",
"Plum",
"Pineapple",
"Pomelo",
"Raspberry",
"Salmonberry",
"Strawberry",
"Ximenia",
"Yuzu",
}
.Select(x => new Fruit{Name = x})
.ToList();
}
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>default</LangVersion>
</PropertyGroup>
@@ -45,7 +45,7 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.14.0" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Scriban" Version="2.1.3" />
<PackageReference Include="Spectre.IO" Version="0.1.0" />
</ItemGroup>

View File

@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{CFE7
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console.Json", "..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj", "{6C96C268-CEEE-478A-A36F-E1450AC33B73}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -71,6 +73,18 @@ Global
{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x64.Build.0 = Release|Any CPU
{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x86.ActiveCfg = Release|Any CPU
{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x86.Build.0 = Release|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x64.ActiveCfg = Debug|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x64.Build.0 = Debug|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x86.ActiveCfg = Debug|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x86.Build.0 = Debug|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|Any CPU.Build.0 = Release|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x64.ActiveCfg = Release|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x64.Build.0 = Release|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x86.ActiveCfg = Release|Any CPU
{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -79,6 +93,7 @@ Global
{F75B882A-06DB-426B-9580-A7302D32E684} = {CFE7445D-F971-429D-B6E6-9E68456AE00F}
{112A37CB-1EFE-4A90-BD5B-5437038BE276} = {CFE7445D-F971-429D-B6E6-9E68456AE00F}
{18A3F32D-FECD-463B-A194-6EE74EA9E5EC} = {CFE7445D-F971-429D-B6E6-9E68456AE00F}
{6C96C268-CEEE-478A-A36F-E1450AC33B73} = {CFE7445D-F971-429D-B6E6-9E68456AE00F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F37FDE3-D591-4D43-8DDE-2ED6BAB0A7B4}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />

View File

@@ -16,11 +16,15 @@ public class FavorInstanceAnsiConsoleOverStaticAnalyzer : SpectreAnalyzer
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
if (ansiConsoleType == null)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
// if this operation isn't an invocation against one of the System.Console methods
// defined in _methods then we can safely stop analyzing and return;
var invocationOperation = (IInvocationOperation)context.Operation;

View File

@@ -17,6 +17,16 @@ public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var liveTypes = Constants.LiveRenderables
.Select(i => compilationStartContext.Compilation.GetTypeByMetadataName(i))
.Where(i => i != null)
.ToImmutableArray();
if (liveTypes.Length == 0)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
@@ -29,27 +39,21 @@ public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
return;
}
var liveTypes = Constants.LiveRenderables
.Select(i => context.Compilation.GetTypeByMetadataName(i))
.ToImmutableArray();
if (liveTypes.All(i => !SymbolEqualityComparer.Default.Equals(i, methodSymbol.ContainingType)))
{
return;
}
#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
var model = context.Operation.SemanticModel!;
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i))
.Select(i => model.GetOperation(i, context.CancellationToken))
.OfType<IInvocationOperation>()
.ToList();
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType)))
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
{
return;
}

View File

@@ -17,6 +17,14 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var ansiConsoleExtensionsType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
if (ansiConsoleType is null && ansiConsoleExtensionsType is null)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
@@ -31,22 +39,17 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
return;
}
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var ansiConsoleExtensionsType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleType) &&
!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
{
return;
}
#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
var model = context.Operation.SemanticModel!;
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i))
.Select(i => model.GetOperation(i, context.CancellationToken))
.OfType<IInvocationOperation>()
.ToList();
@@ -56,7 +59,7 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != "Start" ||
!liveTypes.Contains(parent.TargetMethod.ContainingType)))
!liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
{
return;
}

View File

@@ -18,6 +18,13 @@ public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var systemConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Console");
var spectreConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
if (systemConsoleType == null || spectreConsoleType == null)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
@@ -31,8 +38,6 @@ public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
return;
}
var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console");
if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
{
return;

View File

@@ -1,4 +1,5 @@
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Simplification;
namespace Spectre.Console.Analyzer.CodeActions;
@@ -32,82 +33,170 @@ public class SwitchToAnsiConsoleAction : CodeAction
/// <inheritdoc />
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
{
var originalCaller = ((MemberAccessExpressionSyntax)_originalInvocation.Expression).Name.ToString();
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
var compilation = editor.SemanticModel.Compilation;
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
if (syntaxTree == null)
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
if (operation == null)
{
return _document;
}
var root = (CompilationUnitSyntax)await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
// If there is an ansiConsole passed into the method then we'll use it.
// If there is an IAnsiConsole passed into the method then we'll use it.
// otherwise we'll check for a field level instance.
// if neither of those exist we'll fall back to the static param.
var ansiConsoleParameterDeclaration = GetAnsiConsoleParameterDeclaration();
var ansiConsoleFieldIdentifier = GetAnsiConsoleFieldDeclaration();
var ansiConsoleIdentifier = ansiConsoleParameterDeclaration ??
ansiConsoleFieldIdentifier ??
Constants.StaticInstance;
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
// Replace the System.Console call with a call to the identifier above.
var newRoot = root.ReplaceNode(
_originalInvocation,
GetImportedSpectreCall(originalCaller, ansiConsoleIdentifier));
// If we are calling the static instance and Spectre isn't imported yet we should do so.
if (ansiConsoleIdentifier == Constants.StaticInstance && root.Usings.ToList().All(i => i.Name.ToString() != Constants.SpectreConsole))
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
if (iansiConsoleSymbol != null)
{
newRoot = newRoot.AddUsings(Syntax.SpectreUsing);
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
{
// LookupSymbols check the accessibility of the symbol, but it can
// suggest instance members when the current context is static.
var symbolType = symbol switch
{
IParameterSymbol parameter => parameter.Type,
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
ILocalSymbol local => local.Type,
_ => null,
};
// Locals can be returned even if there are not valid in the current context. For instance,
// it can return locals declared after the current location. Or it can return locals that
// should not be accessible in a static local function.
//
// void Sample()
// {
// int local = 0;
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
// }
//
// Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function.
if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter)
{
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
// The local is not part of the source tree
if (localPosition == null)
{
break;
}
return _document.WithSyntaxRoot(newRoot);
// The local is declared after the current expression
if (localPosition > _originalInvocation.Span.Start)
{
break;
}
private string? GetAnsiConsoleParameterDeclaration()
// The local is declared outside the static local function
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
{
return _originalInvocation
.Ancestors().OfType<MethodDeclarationSyntax>()
.First()
.ParameterList.Parameters
.FirstOrDefault(i => i.Type?.NormalizeWhitespace()?.ToString() == "IAnsiConsole")
?.Identifier.Text;
break;
}
}
private string? GetAnsiConsoleFieldDeclaration()
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
{
// let's look to see if our call is in a static method.
// if so we'll only want to look for static IAnsiConsoles
// and vice-versa if we aren't.
var isStatic = _originalInvocation
.Ancestors()
.OfType<MethodDeclarationSyntax>()
.First()
.Modifiers.Any(i => i.IsKind(SyntaxKind.StaticKeyword));
return _originalInvocation
.Ancestors().OfType<ClassDeclarationSyntax>()
.First()
.Members
.OfType<FieldDeclarationSyntax>()
.FirstOrDefault(i =>
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
(!isStatic ^ i.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))))
?.Declaration.Variables.First().Identifier.Text;
accessibleConsoleSymbol = symbol;
break;
}
}
}
private ExpressionSyntax GetImportedSpectreCall(string originalCaller, string ansiConsoleIdentifier)
if (accessibleConsoleSymbol == null)
{
return ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(ansiConsoleIdentifier),
IdentifierName(originalCaller)))
.WithArgumentList(_originalInvocation.ArgumentList)
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia())
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()))
.Expression;
return _document;
}
// Replace the original invocation
var generator = editor.Generator;
var consoleExpression = accessibleConsoleSymbol switch
{
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
};
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
editor.ReplaceNode(_originalInvocation, newExpression);
return editor.GetChangedDocument();
}
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
{
if (symbol == null)
{
return false;
}
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
{
return true;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
{
return true;
}
}
return false;
}
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
{
// Local functions can be nested, and an instance local function can be declared
// in a static local function. So, you need to continue to check ancestors when a
// local function is not static.
foreach (var member in operation.Syntax.Ancestors())
{
if (member is LocalFunctionStatementSyntax localFunction)
{
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is LambdaExpressionSyntax lambdaExpression)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is MethodDeclarationSyntax methodDeclaration)
{
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
return symbol != null && symbol.IsStatic;
}
}
parentStaticMemberStartPosition = -1;
return false;
}
}

View File

@@ -20,7 +20,7 @@ public class StaticAnsiConsoleToInstanceFix : CodeFixProvider
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root != null)
{
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
var methodDeclaration = root.FindNode(context.Span, getInnermostNodeForTie: true).FirstAncestorOrSelf<InvocationExpressionSyntax>();
if (methodDeclaration != null)
{
context.RegisterCodeFix(

View File

@@ -1,3 +1,5 @@
using Spectre.Console.Cli.Internal.Configuration;
namespace Spectre.Console.Cli;
/// <summary>
@@ -39,10 +41,11 @@ public sealed class CommandApp : ICommandApp
/// Sets the default command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
public void SetDefaultCommand<TCommand>()
/// <returns>A <see cref="DefaultCommandConfigurator"/> that can be used to configure the default command.</returns>
public DefaultCommandConfigurator SetDefaultCommand<TCommand>()
where TCommand : class, ICommand
{
GetConfigurator().SetDefaultCommand<TCommand>();
return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>());
}
/// <summary>

View File

@@ -1,3 +1,5 @@
using Spectre.Console.Cli.Internal.Configuration;
namespace Spectre.Console.Cli;
/// <summary>
@@ -8,6 +10,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
where TDefaultCommand : class, ICommand
{
private readonly CommandApp _app;
private readonly DefaultCommandConfigurator _defaultCommandConfigurator;
/// <summary>
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
@@ -16,7 +19,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
public CommandApp(ITypeRegistrar? registrar = null)
{
_app = new CommandApp(registrar);
_app.GetConfigurator().SetDefaultCommand<TDefaultCommand>();
_defaultCommandConfigurator = _app.SetDefaultCommand<TDefaultCommand>();
}
/// <summary>
@@ -47,4 +50,31 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
{
return _app.RunAsync(args);
}
internal Configurator GetConfigurator()
{
return _app.GetConfigurator();
}
/// <summary>
/// Sets the description of the default command.
/// </summary>
/// <param name="description">The default command description.</param>
/// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns>
public CommandApp<TDefaultCommand> WithDescription(string description)
{
_defaultCommandConfigurator.WithDescription(description);
return this;
}
/// <summary>
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
/// </summary>
/// <param name="data">The data to pass to the default command.</param>
/// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns>
public CommandApp<TDefaultCommand> WithData(object data)
{
_defaultCommandConfigurator.WithData(data);
return this;
}
}

View File

@@ -14,7 +14,7 @@ public abstract class Command<TSettings> : ICommand<TSettings>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate([NotNull] CommandContext context, [NotNull] TSettings settings)
public virtual ValidationResult Validate(CommandContext context, TSettings settings)
{
return ValidationResult.Success();
}
@@ -25,7 +25,7 @@ public abstract class Command<TSettings> : ICommand<TSettings>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute([NotNull] CommandContext context, [NotNull] TSettings settings);
public abstract int Execute(CommandContext context, TSettings settings);
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)

View File

@@ -42,6 +42,13 @@ public class CommandRuntimeException : CommandAppException
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
}
internal static CommandRuntimeException ConversionFailed(MappedCommandParameter parameter, TypeConverter typeConverter, Exception exception)
{
var standardValues = typeConverter.GetStandardValuesSupported() ? typeConverter.GetStandardValues() : null;
var validValues = standardValues == null ? string.Empty : $" Valid values are '{string.Join("', '", standardValues.Cast<object>().Select(Convert.ToString))}'";
return new CommandRuntimeException($"Failed to convert '{parameter.Value}' to {parameter.Parameter.ParameterType.Name}.{validValues}", exception);
}
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
{
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");

View File

@@ -6,6 +6,64 @@ namespace Spectre.Console.Cli;
/// </summary>
public static class ConfiguratorExtensions
{
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="helpProvider">The help provider to use.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.SetHelpProvider(helpProvider);
return configurator;
}
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
where T : IHelpProvider
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.SetHelpProvider<T>();
return configurator;
}
/// <summary>
/// Sets the culture for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="culture">The culture.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
/// <remarks>
/// Text displayed by <see cref="Help.HelpProvider"/> can be localised, but defaults to English.
/// Setting the application culture informs the resource manager which culture to use when fetching strings.
/// English will be used when a culture has not been specified
/// or a string has not been localised for the specified culture.
/// </remarks>
public static IConfigurator SetApplicationCulture(this IConfigurator configurator, CultureInfo? culture)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Culture = culture;
return configurator;
}
/// <summary>
/// Sets the name of the application.
/// </summary>
@@ -181,7 +239,8 @@ public static class ConfiguratorExtensions
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch(
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
public static IBranchConfigurator AddBranch(
this IConfigurator configurator,
string name,
Action<IConfigurator<CommandSettings>> action)
@@ -191,7 +250,7 @@ public static class ConfiguratorExtensions
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
return configurator.AddBranch(name, action);
}
/// <summary>
@@ -201,7 +260,8 @@ public static class ConfiguratorExtensions
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch<TSettings>(
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
public static IBranchConfigurator AddBranch<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Action<IConfigurator<TSettings>> action)
@@ -212,7 +272,7 @@ public static class ConfiguratorExtensions
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
return configurator.AddBranch(name, action);
}
/// <summary>
@@ -235,6 +295,26 @@ public static class ConfiguratorExtensions
return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Adds a command without settings that executes an async delegate.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddAsyncDelegate(
this IConfigurator configurator,
string name,
Func<CommandContext, Task<int>> func)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddAsyncDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
@@ -257,6 +337,28 @@ public static class ConfiguratorExtensions
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Adds a command without settings that executes an async delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddAsyncDelegate<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Func<CommandContext, Task<int>> func)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddAsyncDelegate<TSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Sets the ExceptionsHandler.
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the

View File

@@ -0,0 +1,545 @@
using Spectre.Console.Cli.Resources;
namespace Spectre.Console.Cli.Help;
/// <summary>
/// The help provider for Spectre.Console.
/// </summary>
/// <remarks>
/// Other IHelpProvider implementations can be injected into the CommandApp, if desired.
/// </remarks>
public class HelpProvider : IHelpProvider
{
private HelpProviderResources resources;
/// <summary>
/// Gets a value indicating how many examples from direct children to show in the help text.
/// </summary>
protected virtual int MaximumIndirectExamples { get; }
/// <summary>
/// Gets a value indicating whether any default values for command options are shown in the help text.
/// </summary>
protected virtual bool ShowOptionDefaultValues { get; }
/// <summary>
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text.
/// </summary>
protected virtual bool TrimTrailingPeriod { get; }
private sealed class HelpArgument
{
public string Name { get; }
public int Position { get; set; }
public bool Required { get; }
public string? Description { get; }
public HelpArgument(string name, int position, bool required, string? description)
{
Name = name;
Position = position;
Required = required;
Description = description;
}
public static IReadOnlyList<HelpArgument> Get(ICommandInfo? command)
{
var arguments = new List<HelpArgument>();
arguments.AddRange(command?.Parameters?.OfType<ICommandArgument>()?.Select(
x => new HelpArgument(x.Value, x.Position, x.Required, x.Description))
?? Array.Empty<HelpArgument>());
return arguments;
}
}
private sealed class HelpOption
{
public string? Short { get; }
public string? Long { get; }
public string? Value { get; }
public bool? ValueIsOptional { get; }
public string? Description { get; }
public object? DefaultValue { get; }
public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
{
Short = @short;
Long = @long;
Value = value;
ValueIsOptional = valueIsOptional;
Description = description;
DefaultValue = defaultValue;
}
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources)
{
var parameters = new List<HelpOption>();
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
// Version information applies to the entire application
// Include the "-v" option in the help when at the root of the command line application
// Don't allow the "-v" option if users have specified one or more sub-commands
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
{
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
}
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
new HelpOption(
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(),
o.ValueName, o.ValueIsOptional, o.Description,
o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
?? Array.Empty<HelpOption>());
return parameters;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
/// </summary>
/// <param name="settings">The command line application settings used for configuration.</param>
public HelpProvider(ICommandAppSettings settings)
{
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
resources = new HelpProviderResources(settings.Culture);
}
/// <inheritdoc/>
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
{
var result = new List<IRenderable>();
result.AddRange(GetHeader(model, command));
result.AddRange(GetDescription(model, command));
result.AddRange(GetUsage(model, command));
result.AddRange(GetExamples(model, command));
result.AddRange(GetArguments(model, command));
result.AddRange(GetOptions(model, command));
result.AddRange(GetCommands(model, command));
result.AddRange(GetFooter(model, command));
return result;
}
/// <summary>
/// Gets the header for the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
{
yield break;
}
/// <summary>
/// Gets the description section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command)
{
if (command?.Description == null)
{
yield break;
}
var composer = new Composer();
composer.Style("yellow", $"{resources.Description}:").LineBreak();
composer.Text(command.Description).LineBreak();
yield return composer.LineBreak();
}
/// <summary>
/// Gets the usage section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
{
var composer = new Composer();
composer.Style("yellow", $"{resources.Usage}:").LineBreak();
composer.Tab().Text(model.ApplicationName);
var parameters = new List<string>();
if (command == null)
{
parameters.Add($"[grey][[{resources.Options}]][/]");
parameters.Add($"[aqua]<{resources.Command}>[/]");
}
else
{
foreach (var current in command.Flatten())
{
var isCurrent = current == command;
if (!current.IsDefaultCommand)
{
if (isCurrent)
{
parameters.Add($"[underline]{current.Name.EscapeMarkup()}[/]");
}
else
{
parameters.Add($"{current.Name.EscapeMarkup()}");
}
}
if (current.Parameters.OfType<ICommandArgument>().Any())
{
if (isCurrent)
{
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
{
parameters.Add($"[aqua]<{argument.Value.EscapeMarkup()}>[/]");
}
}
var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.Required).ToArray();
if (optionalArguments.Length > 0 || !isCurrent)
{
foreach (var optionalArgument in optionalArguments)
{
parameters.Add($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]");
}
}
}
if (isCurrent)
{
parameters.Add($"[grey][[{resources.Options}]][/]");
}
}
if (command.IsBranch && command.DefaultCommand == null)
{
// The user must specify the command
parameters.Add($"[aqua]<{resources.Command}>[/]");
}
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
{
// We are on a branch with a default command
// The user can optionally specify the command
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
else if (command.IsDefaultCommand)
{
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
if (commands.Count > 0)
{
// Commands other than the default are present
// So make these optional in the usage statement
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
}
}
composer.Join(" ", parameters);
composer.LineBreak();
return new[]
{
composer,
};
}
/// <summary>
/// Gets the examples section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
/// <remarks>
/// Examples from the command's direct children are used
/// if no examples have been set on the specified command or model.
/// </remarks>
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
{
var maxExamples = int.MaxValue;
var examples = command?.Examples?.ToList() ?? model.Examples?.ToList() ?? new List<string[]>();
if (examples.Count == 0)
{
// Since we're not checking direct examples,
// make sure that we limit the number of examples.
maxExamples = MaximumIndirectExamples;
// Start at the current command (if exists)
// or alternatively commence at the model.
var commandContainer = command ?? (ICommandContainer)model;
var queue = new Queue<ICommandContainer>(new[] { commandContainer });
// Traverse the command tree and look for examples.
// As soon as a node contains commands, bail.
while (queue.Count > 0)
{
var current = queue.Dequeue();
foreach (var child in current.Commands.Where(x => !x.IsHidden))
{
if (child.Examples.Count > 0)
{
examples.AddRange(child.Examples);
}
queue.Enqueue(child);
}
if (examples.Count >= maxExamples)
{
break;
}
}
}
if (Math.Min(maxExamples, examples.Count) > 0)
{
var composer = new Composer();
composer.LineBreak();
composer.Style("yellow", $"{resources.Examples}:").LineBreak();
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
{
var args = string.Join(" ", examples[index]);
composer.Tab().Text(model.ApplicationName).Space().Style("grey", args);
composer.LineBreak();
}
return new[] { composer };
}
return Array.Empty<IRenderable>();
}
/// <summary>
/// Gets the arguments section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command)
{
var arguments = HelpArgument.Get(command);
if (arguments.Count == 0)
{
return Array.Empty<IRenderable>();
}
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup($"[yellow]{resources.Arguments}:[/]"),
new Markup(Environment.NewLine),
};
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
foreach (var argument in arguments.Where(x => x.Required).OrderBy(x => x.Position))
{
grid.AddRow(
$"[silver]<{argument.Name.EscapeMarkup()}>[/]",
argument.Description?.TrimEnd('.') ?? " ");
}
foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position))
{
grid.AddRow(
$"[grey][[{argument.Name.EscapeMarkup()}]][/]",
argument.Description?.TrimEnd('.') ?? " ");
}
result.Add(grid);
return result;
}
/// <summary>
/// Gets the options section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
{
// Collect all options into a single structure.
var parameters = HelpOption.Get(command, resources);
if (parameters.Count == 0)
{
return Array.Empty<IRenderable>();
}
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup($"[yellow]{resources.Options}:[/]"),
new Markup(Environment.NewLine),
};
var helpOptions = parameters.ToArray();
var defaultValueColumn = ShowOptionDefaultValues && helpOptions.Any(e => e.DefaultValue != null);
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
if (defaultValueColumn)
{
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 4, 0) });
}
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
static string GetOptionParts(HelpOption option)
{
var builder = new StringBuilder();
if (option.Short != null)
{
builder.Append('-').Append(option.Short.EscapeMarkup());
if (option.Long != null)
{
builder.Append(", ");
}
}
else
{
builder.Append(" ");
if (option.Long != null)
{
builder.Append(" ");
}
}
if (option.Long != null)
{
builder.Append("--").Append(option.Long.EscapeMarkup());
}
if (option.Value != null)
{
builder.Append(' ');
if (option.ValueIsOptional ?? false)
{
builder.Append("[grey][[").Append(option.Value.EscapeMarkup()).Append("]][/]");
}
else
{
builder.Append("[silver]<").Append(option.Value.EscapeMarkup()).Append(">[/]");
}
}
return builder.ToString();
}
if (defaultValueColumn)
{
grid.AddRow(" ", $"[lime]{resources.Default}[/]", " ");
}
foreach (var option in helpOptions)
{
var columns = new List<string> { GetOptionParts(option) };
if (defaultValueColumn)
{
static string Bold(object obj) => $"[bold]{obj.ToString().EscapeMarkup()}[/]";
var defaultValue = option.DefaultValue switch
{
null => " ",
"" => " ",
Array { Length: 0 } => " ",
Array array => string.Join(", ", array.Cast<object>().Select(Bold)),
_ => Bold(option.DefaultValue),
};
columns.Add(defaultValue);
}
columns.Add(option.Description?.TrimEnd('.') ?? " ");
grid.AddRow(columns.ToArray());
}
result.Add(grid);
return result;
}
/// <summary>
/// Gets the commands section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command)
{
var commandContainer = command ?? (ICommandContainer)model;
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
commands = commands.Where(x => !x.IsHidden).ToList();
if (commands.Count == 0)
{
return Array.Empty<IRenderable>();
}
var result = new List<IRenderable>
{
new Markup(Environment.NewLine),
new Markup($"[yellow]{resources.Commands}:[/]"),
new Markup(Environment.NewLine),
};
var grid = new Grid();
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
foreach (var child in commands)
{
var arguments = new Composer();
arguments.Style("silver", child.Name.EscapeMarkup());
arguments.Space();
foreach (var argument in HelpArgument.Get(child).Where(a => a.Required))
{
arguments.Style("silver", $"<{argument.Name.EscapeMarkup()}>");
arguments.Space();
}
if (TrimTrailingPeriod)
{
grid.AddRow(
arguments.ToString().TrimEnd(),
child.Description?.TrimEnd('.') ?? " ");
}
else
{
grid.AddRow(
arguments.ToString().TrimEnd(),
child.Description ?? " ");
}
}
result.Add(grid);
return result;
}
/// <summary>
/// Gets the footer for the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo? command)
{
yield break;
}
}

View File

@@ -0,0 +1,131 @@
using System.Resources;
namespace Spectre.Console.Cli.Help;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
internal class HelpProviderResources
{
private readonly ResourceManager resourceManager = new ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
private readonly CultureInfo? resourceCulture = null;
public HelpProviderResources()
{
}
public HelpProviderResources(CultureInfo? culture)
{
resourceCulture = culture;
}
/// <summary>
/// Gets the localised string for ARGUMENTS.
/// </summary>
internal string Arguments
{
get
{
return resourceManager.GetString("Arguments", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for COMMAND.
/// </summary>
internal string Command
{
get
{
return resourceManager.GetString("Command", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for COMMANDS.
/// </summary>
internal string Commands
{
get
{
return resourceManager.GetString("Commands", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for DEFAULT.
/// </summary>
internal string Default
{
get
{
return resourceManager.GetString("Default", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for DESCRIPTION.
/// </summary>
internal string Description
{
get
{
return resourceManager.GetString("Description", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for EXAMPLES.
/// </summary>
internal string Examples
{
get
{
return resourceManager.GetString("Examples", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for OPTIONS.
/// </summary>
internal string Options
{
get
{
return resourceManager.GetString("Options", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for Prints help information.
/// </summary>
internal string PrintHelpDescription
{
get
{
return resourceManager.GetString("PrintHelpDescription", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for Prints version information.
/// </summary>
internal string PrintVersionDescription
{
get
{
return resourceManager.GetString("PrintVersionDescription", resourceCulture) ?? string.Empty;
}
}
/// <summary>
/// Gets the localised string for USAGE.
/// </summary>
internal string Usage
{
get
{
return resourceManager.GetString("Usage", resourceCulture) ?? string.Empty;
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command argument.
/// </summary>
public interface ICommandArgument : ICommandParameter
{
/// <summary>
/// Gets the value of the argument.
/// </summary>
string Value { get; }
/// <summary>
/// Gets the position of the argument.
/// </summary>
int Position { get; }
}

View File

@@ -0,0 +1,25 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command container.
/// </summary>
public interface ICommandContainer
{
/// <summary>
/// Gets all the examples for the container.
/// </summary>
IReadOnlyList<string[]> Examples { get; }
/// <summary>
/// Gets all commands in the container.
/// </summary>
IReadOnlyList<ICommandInfo> Commands { get; }
/// <summary>
/// Gets the default command for the container.
/// </summary>
/// <remarks>
/// Returns null if a default command has not been set.
/// </remarks>
ICommandInfo? DefaultCommand { get; }
}

View File

@@ -0,0 +1,42 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents an executable command.
/// </summary>
public interface ICommandInfo : ICommandContainer
{
/// <summary>
/// Gets the name of the command.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of the command.
/// </summary>
string? Description { get; }
/// <summary>
/// Gets a value indicating whether the command is a branch.
/// </summary>
bool IsBranch { get; }
/// <summary>
/// Gets a value indicating whether the command is the default command within its container.
/// </summary>
bool IsDefaultCommand { get; }
/// <summary>
/// Gets a value indicating whether the command is hidden.
/// </summary>
bool IsHidden { get; }
/// <summary>
/// Gets the parameters associated with the command.
/// </summary>
IReadOnlyList<ICommandParameter> Parameters { get; }
/// <summary>
/// Gets the parent command, if any.
/// </summary>
ICommandInfo? Parent { get; }
}

View File

@@ -0,0 +1,23 @@
namespace Spectre.Console.Cli.Help;
internal static class ICommandInfoExtensions
{
/// <summary>
/// Walks up the command.Parent tree, adding each command into a list as it goes.
/// </summary>
/// <remarks>The first command added to the list is the current (ie. this one).</remarks>
/// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns>
public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo)
{
var result = new Stack<Help.ICommandInfo>();
var current = commandInfo;
while (current != null)
{
result.Push(current);
current = current.Parent;
}
return result.ToList();
}
}

View File

@@ -0,0 +1,12 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command model.
/// </summary>
public interface ICommandModel : ICommandContainer
{
/// <summary>
/// Gets the name of the application.
/// </summary>
string ApplicationName { get; }
}

View File

@@ -0,0 +1,27 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command option.
/// </summary>
public interface ICommandOption : ICommandParameter
{
/// <summary>
/// Gets the long names of the option.
/// </summary>
IReadOnlyList<string> LongNames { get; }
/// <summary>
/// Gets the short names of the option.
/// </summary>
IReadOnlyList<string> ShortNames { get; }
/// <summary>
/// Gets the value name of the option, if applicable.
/// </summary>
string? ValueName { get; }
/// <summary>
/// Gets a value indicating whether the option value is optional.
/// </summary>
bool ValueIsOptional { get; }
}

View File

@@ -0,0 +1,32 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command parameter.
/// </summary>
public interface ICommandParameter
{
/// <summary>
/// Gets a value indicating whether the parameter is a flag.
/// </summary>
bool IsFlag { get; }
/// <summary>
/// Gets a value indicating whether the parameter is required.
/// </summary>
bool Required { get; }
/// <summary>
/// Gets the description of the parameter.
/// </summary>
string? Description { get; }
/// <summary>
/// Gets the default value of the parameter, if specified.
/// </summary>
DefaultValueAttribute? DefaultValue { get; }
/// <summary>
/// Gets a value indicating whether the parameter is hidden.
/// </summary>
bool IsHidden { get; }
}

Some files were not shown because too many files have changed in this diff Show More