Compare commits

...

13 Commits

Author SHA1 Message Date
Patrik Svensson
d70ad661fc Add docs for text prompts 2020-11-19 14:25:24 +01:00
Patrik Svensson
0d209d8f18 Add text prompt support 2020-11-19 12:24:04 +01:00
Patrik Svensson
380c6aca45 Add net5.0 target framework 2020-11-11 15:28:32 +01:00
Patrik Svensson
b1da5e7ba8 Add support for markup text in panel header 2020-11-07 20:43:53 +01:00
Patrik Svensson
be3350a411 Bump dotnet-example to 1.0.0 2020-11-03 18:14:41 +01:00
Patrik Svensson
a1d11e9d0c Add support for moving the cursor 2020-10-28 18:57:08 +01:00
Patrik Svensson
93d1971f48 Update rule example and docs 2020-10-27 17:30:50 +01:00
Martin Andersen
bca1c889d1 Add Padding extension
Add extension method for specifying horizontal and vertical padding.
Similar constructor for `Padding` already existed.

Update documentation for table column appearance (padding).

Update
`Should_Render_Padded_Object_Correctly_When_Nested_Within_Other_Object`
test to use new extension method.
2020-10-27 09:09:30 +01:00
Takahito Yamatoya
9915a0d6a8 Update Documentation for escape 2020-10-27 09:08:27 +01:00
Takahito Yamatoya
f34fc43d00 Update documentation for aligning tables 2020-10-27 09:08:27 +01:00
Patrik Svensson
e7f497050c Add row and column accessors for tables and grids 2020-10-26 18:15:27 +01:00
Martin Andersen
3e5e22d6c2 Update documentation
- ExceptionFormat -> ExceptionFormats.
- Fix link to documentation.
- Add cross reference to Styles.
- Render table in example code.
- Add code for setting background color.
2020-10-26 15:31:44 +01:00
Patrik Svensson
10daf727e9 Fix outdated documentation 2020-10-26 12:20:17 +01:00
123 changed files with 2935 additions and 738 deletions

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '3.1.301' # SDK Version to use. dotnet-version: 5.0.100
- name: Build - name: Build
shell: bash shell: bash
@@ -55,10 +55,15 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup dotnet - name: Setup dotnet 3.1.402
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 3.1.301 dotnet-version: 3.1.402
- name: Setup dotnet 5.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
- name: Integration Tests - name: Integration Tests
shell: bash shell: bash

View File

@@ -24,7 +24,7 @@ jobs:
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '3.1.301' # SDK Version to use. dotnet-version: 5.0.100
- name: Publish - name: Publish
shell: bash shell: bash

View File

@@ -29,7 +29,7 @@ jobs:
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '3.1.301' # SDK Version to use. dotnet-version: 5.0.100
- name: Build - name: Build
shell: bash shell: bash
@@ -64,10 +64,15 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup dotnet - name: Setup dotnet 3.1.402
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 3.1.301 dotnet-version: 3.1.402
- name: Setup dotnet 5.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
- name: Build - name: Build
shell: bash shell: bash
@@ -90,10 +95,15 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup dotnet - name: Setup dotnet 3.1.402
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 3.1.301 dotnet-version: 3.1.402
- name: Setup dotnet 5.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
- name: Publish - name: Publish
shell: bash shell: bash

View File

@@ -88,7 +88,7 @@ Spectre.Consoleでできることを見るために、
``` ```
> dotnet tool install -g dotnet-example > dotnet tool restore
``` ```
このリポジトリで提供している例が一覧表示されます このリポジトリで提供している例が一覧表示されます
@@ -381,8 +381,8 @@ AnsiConsole.WriteException(ex);
```csharp ```csharp
AnsiConsole.WriteException(ex, AnsiConsole.WriteException(ex,
ExceptionFormat.ShortenPaths | ExceptionFormat.ShortenTypes | ExceptionFormats.ShortenPaths | ExceptionFormats.ShortenTypes |
ExceptionFormat.ShortenMethods | ExceptionFormat.ShowLinks); ExceptionFormats.ShortenMethods | ExceptionFormats.ShowLinks);
``` ```
![exception](docs/input/assets/images/compact_exception.png) ![exception](docs/input/assets/images/compact_exception.png)

View File

@@ -49,7 +49,7 @@ regular `System.Console` API.
If the current terminal does not support ANSI escape sequences, If the current terminal does not support ANSI escape sequences,
`Spectre.Console` will fallback to using the `System.Console` API. `Spectre.Console` will fallback to using the `System.Console` API.
_NOTE: This library is currently under development and API's _NOTE: This library is currently under development and APIs
might change or get removed at any point up until a 1.0 release._ might change or get removed at any point up until a 1.0 release._
### Using the static API ### Using the static API
@@ -100,7 +100,7 @@ To see Spectre.Console in action, install the
global tool. global tool.
``` ```
> dotnet tool install -g dotnet-example > dotnet tool restore
``` ```
Now you can list available examples in this repository: Now you can list available examples in this repository:

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<DefaultItemExcludes>$(DefaultItemExcludes);output\**;.gitignore</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);output\**;.gitignore</DefaultItemExcludes>
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip> <MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
@@ -31,8 +31,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Statiq.Web" Version="1.0.0-beta.5" /> <PackageReference Include="Statiq.Web" Version="1.0.0-beta.13" />
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.0" /> <PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -10,7 +10,7 @@ The documentation site uses [Statiq](https://statiq.dev), a static site generato
> dotnet run preview --virtual-dir "spectre.console" > dotnet run preview --virtual-dir "spectre.console"
``` ```
After the build is complete, you can navigate to [http://localhost:5080/spectre.consle](http://localhost:5080/spectre.console). After the build is complete, you can navigate to [http://localhost:5080/spectre.console](http://localhost:5080/spectre.console).
**Note that the site runs under a virtual directory.** **Note that the site runs under a virtual directory.**

View File

@@ -130,7 +130,7 @@
<div class="sidebar-nav-item @(Document.IdEquals(root) ? "active" : null)"> <div class="sidebar-nav-item @(Document.IdEquals(root) ? "active" : null)">
@if(root.ShowLink()) @if(root.ShowLink())
{ {
@Html.DocumentLink(root) @Html.DocumentLink(root)
} }
else else
{ {
@@ -140,6 +140,11 @@
@foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible()) @foreach (IDocument document in OutputPages.GetChildrenOf(root).OnlyVisible())
{ {
if(string.IsNullOrWhiteSpace(document.GetTitle()))
{
continue;
}
DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document); DocumentList<IDocument> documentChildren = OutputPages.GetChildrenOf(document);
<div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)"> <div class="sidebar-nav-item @(Document.IdEquals(document) ? "active" : null) @(documentChildren.Any() ? "has-children" : null)">
@if(document.ShowLink()) @if(document.ShowLink())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -19,8 +19,8 @@ the hyperlinks are clickable is up to the terminal.
```csharp ```csharp
AnsiConsole.WriteException(ex, AnsiConsole.WriteException(ex,
ExceptionFormat.ShortenPaths | ExceptionFormat.ShortenTypes | ExceptionFormats.ShortenPaths | ExceptionFormats.ShortenTypes |
ExceptionFormat.ShortenMethods | ExceptionFormat.ShowLinks); ExceptionFormats.ShortenMethods | ExceptionFormats.ShowLinks);
``` ```
<img src="assets/images/compact_exception.png" style="max-width: 100%;"> <img src="assets/images/compact_exception.png" style="max-width: 100%;">
@@ -36,15 +36,15 @@ AnsiConsole.WriteException(ex, new ExceptionSettings
Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks, Format = ExceptionFormats.ShortenEverything | ExceptionFormats.ShowLinks,
Style = new ExceptionStyle Style = new ExceptionStyle
{ {
Exception = Style.WithForeground(Color.Grey), Exception = new Style().Foreground(Color.Grey),
Message = Style.WithForeground(Color.White), Message = new Style().Foreground(Color.White),
NonEmphasized = Style.WithForeground(Color.Cornsilk1), NonEmphasized = new Style().Foreground(Color.Cornsilk1),
Parenthesis = Style.WithForeground(Color.Cornsilk1), Parenthesis = new Style().Foreground(Color.Cornsilk1),
Method = Style.WithForeground(Color.Red), Method = new Style().Foreground(Color.Red),
ParameterName = Style.WithForeground(Color.Cornsilk1), ParameterName = new Style().Foreground(Color.Cornsilk1),
ParameterType = Style.WithForeground(Color.Red), ParameterType = new Style().Foreground(Color.Red),
Path = Style.WithForeground(Color.Red), Path = new Style().Foreground(Color.Red),
LineNumber = Style.WithForeground(Color.Cornsilk1), LineNumber = new Style().Foreground(Color.Cornsilk1),
} }
}); });
``` ```

View File

@@ -1,4 +1,4 @@
Title: Welcome Title: Welcome
Order: 0 Order: 0
--- ---

View File

@@ -6,7 +6,7 @@ The class `Markup` allows you to output rich text to the console.
# Syntax # Syntax
Console markup uses a syntax inspired by bbcode. If you write the style (see Styles) Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`. in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
```csharp ```csharp
@@ -21,6 +21,7 @@ rendering of `IRenderable` also have overloads for rendering rich text.
var table = new Table(); var table = new Table();
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]"))); table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
table.AddColumn(new TableColumn("[blue]Bar[/]")); table.AddColumn(new TableColumn("[blue]Bar[/]"));
AnsiConsole.Render(table);
``` ```
# Convenience methods # Convenience methods
@@ -43,20 +44,24 @@ AnsiConsole.Markup("[[Hello]] "); // [Hello]
AnsiConsole.Markup("[red][[World]][/]"); // [World] AnsiConsole.Markup("[red][[World]][/]"); // [World]
``` ```
You can also use the `SafeMarkup` extension method. You can also use the `EscapeMarkup` extension method.
```csharp ```csharp
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".SafeMarkup()); AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
``` ```
You can also use the `Markup.Escape` method.
```csharp
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
```
# Setting background color # Setting background color
You can set the background color in markup by prefixing the color with You can set the background color in markup by prefixing the color with
`on`. `on`.
``` ```csharp
[bold yellow on blue]Hello[/] AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
[default on blue]World[/] AnsiConsole.Markup("[default on blue]World[/]");
``` ```
# Rendering emojis # Rendering emojis
@@ -64,7 +69,7 @@ You can set the background color in markup by prefixing the color with
To output an emoji as part of markup, you can use emoji shortcodes. To output an emoji as part of markup, you can use emoji shortcodes.
```csharp ```csharp
AnsiConsole.MarkupLine("Hello :globe_showing_europe_africa:!"); AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
``` ```
For a list of emoji, see the [Emojis](xref:emojis) appendix section. For a list of emoji, see the [Emojis](xref:emojis) appendix section.

99
docs/input/prompt.md Normal file
View File

@@ -0,0 +1,99 @@
Title: Prompt
Order: 4
---
Sometimes you want to get some input from the user, and for this
you can use the `Prompt<TResult>`.
# Confirmation
```csharp
if (!AnsiConsole.Confirm("Run example?"))
{
return;
}
```
```text
Run example? [y/n] (y): _
```
# Simple
```csharp
// Ask for the user's name
string name = AnsiConsole.Ask<string>("What's your [green]name[/]?");
// Ask for the user's age
int age = AnsiConsole.Ask<int>("What's your [green]age[/]?");
```
```text
What's your name? Patrik
What's your age? 37
```
# Choices
```csharp
var fruit = AnsiConsole.Prompt(
new TextPrompt<string>("What's your [green]favorite fruit[/]?")
.InvalidChoiceMessage("[red]That's not a valid fruit[/]")
.DefaultValue("Orange")
.AddChoice("Apple")
.AddChoice("Banana")
.AddChoice("Orange"));
```
```text
What's your favorite fruit? [Apple/Banana/Orange] (Orange): _
```
# Validation
```csharp
var age = AnsiConsole.Prompt(
new TextPrompt<int>("What's the secret number?")
.Validate(age =>
{
return age switch
{
<= 99 => ValidationResult.Error("[red]Too low[/]"),
>= 99 => ValidationResult.Error("[red]Too high[/]"),
_ => ValidationResult.Success(),
};
}));
```
```text
What's the secret number? 32
Too low
What's the secret number? 102
Too high
What's the secret number? _
```
# Secrets
```csharp
var password = AnsiConsole.Prompt(
new TextPrompt<string>("Enter [green]password[/]")
.PromptStyle("red")
.Secret());
```
```text
Enter password: ************_
```
# Optional
```csharp
var color = AnsiConsole.Prompt(
new TextPrompt<string>("[grey][[Optional]][/] [green]Favorite color[/]?")
.AllowEmpty());
```
```text
[Optional] Favorite color? _
```

View File

@@ -12,7 +12,9 @@ To render a calendar, create a `Calendar` instance with a target date.
```csharp ```csharp
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020,10);
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
```
```text
2020 October 2020 October
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │
@@ -32,20 +34,22 @@ You can set the calendar's culture to show localized weekdays.
```csharp ```csharp
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020,10);
calendar.SetCulture("ja-JP"); calendar.Culture("ja-JP");
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
```
202010 ```text
┌────┬────┬────┬────┬────┬────┬────┐ Oktober 2020
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
├────┼────┼────┼────┼────┼────┼────┤ │ Mån │ Tis │ Ons │ Tor │ Fre │ Lör │ Sön │
1 2 3 ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
4 5 6 7 8 9 10 │ │ │ │ 1 │ 2 │ 3 │ 4 │
11 12 13 14 15 16 17 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11* │
18 19 20 21 22 23 24 │ 12 │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │
25 26 27 28 29 30 31 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │
│ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │
└────┴────┴────┴────┴────┴────┴────┘ │ │ │ │ │ │ │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┘
``` ```
## Header ## Header
@@ -54,9 +58,11 @@ You can hide the calendar header.
```csharp ```csharp
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020,10);
calendar.ShowHeader = false; calendar.ShowHeader();
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
```
```text
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
@@ -72,13 +78,13 @@ AnsiConsole.Render(calendar);
You can set the header style of the calendar. You can set the header style of the calendar.
```csharp ```csharp
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020, 10);
calendar.SetHeaderStyle(Style.Parse("blue bold")); calendar.HeaderStyle(Style.Parse("blue bold"));
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
``` ```
## Calendar Event ## Calendar Events
You can add an event to the calendar. You can add an event to the calendar.
If a date has an event associated with it, the date gets highlighted in the calendar. If a date has an event associated with it, the date gets highlighted in the calendar.
@@ -87,7 +93,9 @@ If a date has an event associated with it, the date gets highlighted in the cale
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020,10);
calendar.AddCalendarEvent(2020, 10, 11); calendar.AddCalendarEvent(2020, 10, 11);
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
```
```text
2020 October 2020 October
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ │ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │
@@ -106,9 +114,8 @@ AnsiConsole.Render(calendar);
You can set the highlight style for a calendar event via `SetHighlightStyle`. You can set the highlight style for a calendar event via `SetHighlightStyle`.
```csharp ```csharp
var calendar = new Calendar(2020,10); var calendar = new Calendar(2020, 10);
calendar.AddCalendarEvent(2020, 10, 11); calendar.AddCalendarEvent(2020, 10, 11);
calendar.SetHighlightStyle(Style.Parse("yellow bold")); calendar.HighlightStyle(Style.Parse("yellow bold"));
AnsiConsole.Render(calendar); AnsiConsole.Render(calendar);
``` ```

View File

@@ -1,11 +1,11 @@
Title: Rule Title: Rule
Order: 5 Order: 5
RedirectFrom: rule RedirectFrom: rule
--- ---
The `Rule` class is used to render a horizontal rule (line) to the terminal. The `Rule` class is used to render a horizontal rule (line) to the terminal.
![Rule](../assets/images/rule.png) <img src="../assets/images/rule.png" style="width: 100%;" />
# Usage # Usage
@@ -23,8 +23,9 @@ You can set the rule title markup text.
```csharp ```csharp
var rule = new Rule("[red]Hello[/]"); var rule = new Rule("[red]Hello[/]");
AnsiConsole.Render(rule); AnsiConsole.Render(rule);
```
// output ```text
───────────────────────────────── Hello ───────────────────────────────── ───────────────────────────────── Hello ─────────────────────────────────
``` ```
@@ -36,36 +37,36 @@ You can set the rule's title alignment.
var rule = new Rule("[red]Hello[/]"); var rule = new Rule("[red]Hello[/]");
rule.Alignment = Justify.Left; rule.Alignment = Justify.Left;
AnsiConsole.Render(rule); AnsiConsole.Render(rule);
```
//output ```text
── Hello ──────────────────────────────────────────────────────────────── ── Hello ────────────────────────────────────────────────────────────────
``` ```
You can also specify it with a method You can also specify it via an extension method:
```csharp ```csharp
var rule = new Rule("[red]Hello[/]"); var rule = new Rule("[red]Hello[/]");
rule.LeftAligned(); rule.LeftAligned();
AnsiConsole.Render(rule); AnsiConsole.Render(rule);
```
//output ```text
── Hello ──────────────────────────────────────────────────────────────── ── Hello ────────────────────────────────────────────────────────────────
``` ```
## Style ## Styling
You can set the rule style.
```csharp ```csharp
var rule = new Rule("[red]Hello[/]"); var rule = new Rule("[red]Hello[/]");
rule.Style = Style.Parse("red dim"); rule.Style = Style.Parse("red dim");
AnsiConsole.Render(rule); AnsiConsole.Render(rule);
``` ```
You can also specify it with a method You can also specify it via an extension method
```csharp ```csharp
var rule = new Rule("[red]Hello[/]"); var rule = new Rule("[red]Hello[/]");
rule.SetStyle("red dim"); rule.RuleStyle("red dim");
AnsiConsole.Render(rule); AnsiConsole.Render(rule);
``` ```

View File

@@ -51,10 +51,10 @@ For a list of borders, see the [Borders](xref:borders) appendix section.
```csharp ```csharp
// Sets the border // Sets the border
table.SetBorder(Border.None); table.Border(TableBorder.None);
table.SetBorder(Border.Ascii); table.Border(TableBorder.Ascii);
table.SetBorder(Border.Square); table.Border(TableBorder.Square);
table.SetBorder(Border.Rounded); table.Border(TableBorder.Rounded);
``` ```
## Expand / Collapse ## Expand / Collapse
@@ -79,7 +79,16 @@ table.HideHeaders();
```csharp ```csharp
// Sets the table width to 50 cells // Sets the table width to 50 cells
table.SetWidth(50); table.Width(50);
```
## Alignment
```csharp
table.Alignment(Justify.Right);
table.RightAligned();
table.Centered();
table.LeftAligned();
``` ```
# Column appearance # Column appearance
@@ -91,31 +100,37 @@ table.SetWidth(50);
## Alignment ## Alignment
```csharp ```csharp
// Set the alignment explicitly table.Columns[0].Alignment(Justify.Right);
column.SetAlignment(Justify.Right); table.Columns[0].LeftAligned();
table.Columns[0].Centered();
table.Columns[0].RightAligned();
``` ```
## Padding ## Padding
```csharp ```csharp
// Set left and right padding // Set padding individually
column.SetPadding(left: 3, right: 5); table.Columns[0].PadLeft(3);
table.Columns[0].PadRight(5);
// Set padding individually. // Or chained together
column.PadLeft(3); table.Columns[0].PadLeft(3).PadRight(5);
column.PadRight(5);
// Or with the shorthand method if the left and right
// padding are identical. Vertical padding is ignored.
table.Columns[0].Padding(4, 0);
``` ```
## Disable column wrapping ## Disable column wrapping
```csharp ```csharp
// Disable column wrapping // Disable column wrapping
column.NoWrap(); table.Columns[0].NoWrap();
``` ```
## Set column width ## Set column width
```csharp ```csharp
// Set the column width (no fluent extension method for this yet) // Set the column width
column.Width = 15; table.Columns[0].Width(15);
``` ```

View File

@@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"cake.tool": { "cake.tool": {
"version": "0.38.4", "version": "1.0.0-rc0001",
"commands": [ "commands": [
"dotnet-cake" "dotnet-cake"
] ]
@@ -15,7 +15,7 @@
] ]
}, },
"dotnet-example": { "dotnet-example": {
"version": "0.8.0", "version": "1.1.0",
"commands": [ "commands": [
"dotnet-example" "dotnet-example"
] ]

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Borders</Title> <Title>Borders</Title>
<Description>Demonstrates the different kind of borders.</Description> <Description>Demonstrates the different kind of borders.</Description>

View File

@@ -1,4 +1,3 @@
using System.Diagnostics;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
@@ -22,7 +21,7 @@ namespace BordersExample
static IRenderable CreatePanel(string name, BoxBorder border) 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($" {name} ", Style.Parse("blue"), Justify.Center) .Header($" [blue]{name}[/] ", Justify.Center)
.Border(border) .Border(border)
.BorderStyle(Style.Parse("grey")); .BorderStyle(Style.Parse("grey"));
} }
@@ -54,7 +53,7 @@ namespace BordersExample
table.AddRow("Cell", "Cell"); table.AddRow("Cell", "Cell");
return new Panel(table) return new Panel(table)
.Header($" {name} ", Style.Parse("blue"), Justify.Center) .Header($" [blue]{name}[/] ", Justify.Center)
.NoBorder(); .NoBorder();
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Calendars</Title> <Title>Calendars</Title>
<Description>Demonstrates how to render calendars.</Description> <Description>Demonstrates how to render calendars.</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Colors</Title> <Title>Colors</Title>
<Description>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</Description> <Description>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Columns</Title> <Title>Columns</Title>
<Description>Demonstrates how to render data into columns.</Description> <Description>Demonstrates how to render data into columns.</Description>

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
<Title>Cursor</Title>
<Description>Demonstrates how to move the cursor.</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using Spectre.Console;
namespace Cursor
{
public static class Program
{
public static void Main(string[] args)
{
AnsiConsole.Write("Hello");
// Move the cursor 3 cells to the right
AnsiConsole.Cursor.Move(CursorDirection.Right, 3);
AnsiConsole.Write("World");
// Move the cursor 5 cells to the left.
AnsiConsole.Cursor.Move(CursorDirection.Left, 5);
AnsiConsole.WriteLine("Universe");
}
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Emojis</Title> <Title>Emojis</Title>
<Description>Demonstrates how to render emojis.</Description> <Description>Demonstrates how to render emojis.</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Exceptions</Title> <Title>Exceptions</Title>
<Description>Demonstrates how to render formatted exceptions.</Description> <Description>Demonstrates how to render formatted exceptions.</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Grids</Title> <Title>Grids</Title>
<Description>Demonstrates how to render grids in a console.</Description> <Description>Demonstrates how to render grids in a console.</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Info</Title> <Title>Info</Title>
<Description>Displays the capabilities of the current console.</Description> <Description>Displays the capabilities of the current console.</Description>

View File

@@ -1,3 +1,4 @@
using System;
using Spectre.Console; using Spectre.Console;
namespace InfoExample namespace InfoExample
@@ -12,6 +13,7 @@ namespace InfoExample
.AddRow("[b]Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}") .AddRow("[b]Color system[/]", $"{AnsiConsole.Capabilities.ColorSystem}")
.AddRow("[b]Supports ansi?[/]", $"{YesNo(AnsiConsole.Capabilities.SupportsAnsi)}") .AddRow("[b]Supports ansi?[/]", $"{YesNo(AnsiConsole.Capabilities.SupportsAnsi)}")
.AddRow("[b]Legacy console?[/]", $"{YesNo(AnsiConsole.Capabilities.LegacyConsole)}") .AddRow("[b]Legacy console?[/]", $"{YesNo(AnsiConsole.Capabilities.LegacyConsole)}")
.AddRow("[b]Interactive?[/]", $"{YesNo(Environment.UserInteractive)}")
.AddRow("[b]Buffer width[/]", $"{AnsiConsole.Console.Width}") .AddRow("[b]Buffer width[/]", $"{AnsiConsole.Console.Width}")
.AddRow("[b]Buffer height[/]", $"{AnsiConsole.Console.Height}"); .AddRow("[b]Buffer height[/]", $"{AnsiConsole.Console.Height}");

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Links</Title> <Title>Links</Title>
<Description>Demonstrates how to render links in a console.</Description> <Description>Demonstrates how to render links in a console.</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Panels</Title> <Title>Panels</Title>
<Description>Demonstrates how to render items in panels.</Description> <Description>Demonstrates how to render items in panels.</Description>

View File

@@ -20,16 +20,14 @@ namespace PanelExample
new Panel(new Text("Left adjusted\nLeft").LeftAligned()) new Panel(new Text("Left adjusted\nLeft").LeftAligned())
.Expand() .Expand()
.SquareBorder() .SquareBorder()
.Header("Left") .Header("[red]Left[/]"));
.HeaderStyle("red"));
// Centered ASCII panel with text // Centered ASCII panel with text
AnsiConsole.Render( AnsiConsole.Render(
new Panel(new Text("Centered\nCenter").Centered()) new Panel(new Text("Centered\nCenter").Centered())
.Expand() .Expand()
.AsciiBorder() .AsciiBorder()
.Header("Center") .Header("[green]Center[/]")
.HeaderStyle("green")
.HeaderAlignment(Justify.Center)); .HeaderAlignment(Justify.Center));
// Right adjusted, rounded panel with text // Right adjusted, rounded panel with text
@@ -37,8 +35,7 @@ namespace PanelExample
new Panel(new Text("Right adjusted\nRight").RightAligned()) new Panel(new Text("Right adjusted\nRight").RightAligned())
.Expand() .Expand()
.RoundedBorder() .RoundedBorder()
.Header("Right") .Header("[blue]Right[/]")
.HeaderStyle("blue")
.HeaderAlignment(Justify.Right)); .HeaderAlignment(Justify.Right));
} }
} }

View File

@@ -0,0 +1,77 @@
using Spectre.Console;
namespace Cursor
{
public static class Program
{
public static void Main(string[] args)
{
// Confirmation
if (!AnsiConsole.Confirm("Run prompt example?"))
{
AnsiConsole.MarkupLine("Ok... :(");
return;
}
// String
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned());
var name = AnsiConsole.Ask<string>("What's your [green]name[/]?");
// String with choices
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned());
var fruit = AnsiConsole.Prompt(
new TextPrompt<string>("What's your [green]favorite fruit[/]?")
.InvalidChoiceMessage("[red]That's not a valid fruit[/]")
.DefaultValue("Orange")
.AddChoice("Apple")
.AddChoice("Banana")
.AddChoice("Orange"));
// Integer
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned());
var age = AnsiConsole.Prompt(
new TextPrompt<int>("How [green]old[/] are you?")
.PromptStyle("green")
.ValidationErrorMessage("[red]That's not a valid age[/]")
.Validate(age =>
{
return age switch
{
<= 0 => ValidationResult.Error("[red]You must at least be 1 years old[/]"),
>= 123 => ValidationResult.Error("[red]You must be younger than the oldest person alive[/]"),
_ => ValidationResult.Success(),
};
}));
// Secret
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned());
var password = AnsiConsole.Prompt(
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret());
// Optional
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned());
var color = AnsiConsole.Prompt(
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
.AllowEmpty());
// Summary
AnsiConsole.WriteLine();
AnsiConsole.Render(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned());
AnsiConsole.Render(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
.RoundedBorder()
.BorderColor(Color.Grey)
.AddRow("[grey]Name[/]", name)
.AddRow("[grey]Favorite fruit[/]", fruit)
.AddRow("[grey]Age[/]", age.ToString())
.AddRow("[grey]Password[/]", password)
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
}
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>9</LangVersion>
<IsPackable>false</IsPackable>
<Title>Prompt</Title>
<Description>Demonstrates how to get input from a user.</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,30 +10,33 @@ namespace EmojiExample
WrapInPanel( WrapInPanel(
new Rule() new Rule()
.RuleStyle(Style.Parse("yellow")) .RuleStyle(Style.Parse("yellow"))
.AsciiBorder()
.LeftAligned()); .LeftAligned());
// Left aligned title // Left aligned title
WrapInPanel( WrapInPanel(
new Rule("[white]Left aligned[/]") new Rule("[blue]Left aligned[/]")
.RuleStyle(Style.Parse("red")) .RuleStyle(Style.Parse("red"))
.DoubleBorder()
.LeftAligned()); .LeftAligned());
// Centered title // Centered title
WrapInPanel( WrapInPanel(
new Rule("[silver]Centered[/]") new Rule("[green]Centered[/]")
.RuleStyle(Style.Parse("green")) .RuleStyle(Style.Parse("green"))
.HeavyBorder()
.Centered()); .Centered());
// Right aligned title // Right aligned title
WrapInPanel( WrapInPanel(
new Rule("[grey]Right aligned[/]") new Rule("[red]Right aligned[/]")
.RuleStyle(Style.Parse("blue")) .RuleStyle(Style.Parse("blue"))
.RightAligned()); .RightAligned());
} }
private static void WrapInPanel(Rule rule) private static void WrapInPanel(Rule rule)
{ {
AnsiConsole.Render(new Panel(rule).Expand().BorderStyle(Style.Parse("grey"))); AnsiConsole.Render(rule);
AnsiConsole.WriteLine(); AnsiConsole.WriteLine();
} }
} }

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Rules</Title> <Title>Rules</Title>
<Description>Demonstrates how to render horizontal rules (lines).</Description> <Description>Demonstrates how to render horizontal rules (lines).</Description>

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Title>Tables</Title> <Title>Tables</Title>
<Description>Demonstrates how to render tables in a console.</Description> <Description>Demonstrates how to render tables in a console.</Description>

View File

@@ -1,7 +1,7 @@
{ {
"projects": [ "src" ], "projects": [ "src" ],
"sdk": { "sdk": {
"version": "3.1.301", "version": "5.0.100",
"rollForward": "latestPatch" "rollForward": "latestPatch"
} }
} }

View File

@@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup Label="Settings"> <PropertyGroup Label="Settings">
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<LangVersion>8.0</LangVersion> <LangVersion>9.0</LangVersion>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip> <MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFrameworks>net5.0;netcoreapp3.1</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,44 +0,0 @@
using System;
using System.IO;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Tests.Tools
{
public sealed class MarkupConsoleFixture : IDisposable, IAnsiConsole
{
private readonly StringWriter _writer;
private readonly IAnsiConsole _console;
public string Output => _writer.ToString().TrimEnd('\n');
public Capabilities Capabilities => _console.Capabilities;
public Encoding Encoding => _console.Encoding;
public int Width { get; }
public int Height => _console.Height;
public MarkupConsoleFixture(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
{
_writer = new StringWriter();
_console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = ansi,
ColorSystem = (ColorSystemSupport)system,
Out = _writer,
LinkIdentityGenerator = new TestLinkIdentityGenerator(),
});
Width = width;
}
public void Dispose()
{
_writer?.Dispose();
}
public void Write(Segment segment)
{
_console.Write(segment);
}
}
}

View File

@@ -11,10 +11,14 @@ namespace Spectre.Console.Tests
{ {
public Capabilities Capabilities { get; } public Capabilities Capabilities { get; }
public Encoding Encoding { get; } public Encoding Encoding { get; }
public IAnsiConsoleCursor Cursor => throw new NotSupportedException();
public TestableConsoleInput Input { get; }
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }
IAnsiConsoleInput IAnsiConsole.Input => Input;
public Decoration Decoration { get; set; } public Decoration Decoration { get; set; }
public Color Foreground { get; set; } public Color Foreground { get; set; }
public Color Background { get; set; } public Color Background { get; set; }
@@ -35,6 +39,7 @@ namespace Spectre.Console.Tests
Width = width; Width = width;
Height = height; Height = height;
Writer = new StringWriter(); Writer = new StringWriter();
Input = new TestableConsoleInput();
} }
public void Dispose() public void Dispose()
@@ -42,6 +47,10 @@ namespace Spectre.Console.Tests
Writer.Dispose(); Writer.Dispose();
} }
public void Clear(bool home)
{
}
public void Write(Segment segment) public void Write(Segment segment)
{ {
if (segment is null) if (segment is null)

View File

@@ -17,6 +17,10 @@ namespace Spectre.Console.Tests
public Encoding Encoding => _console.Encoding; public Encoding Encoding => _console.Encoding;
public int Width { get; } public int Width { get; }
public int Height => _console.Height; public int Height => _console.Height;
public IAnsiConsoleCursor Cursor => _console.Cursor;
public TestableConsoleInput Input { get; }
IAnsiConsoleInput IAnsiConsole.Input => Input;
public TestableAnsiConsole(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80) public TestableAnsiConsole(ColorSystem system, AnsiSupport ansi = AnsiSupport.Yes, int width = 80)
{ {
@@ -30,6 +34,7 @@ namespace Spectre.Console.Tests
}); });
Width = width; Width = width;
Input = new TestableConsoleInput();
} }
public void Dispose() public void Dispose()
@@ -37,6 +42,11 @@ namespace Spectre.Console.Tests
_writer?.Dispose(); _writer?.Dispose();
} }
public void Clear(bool home)
{
_console.Clear(home);
}
public void Write(Segment segment) public void Write(Segment segment)
{ {
_console.Write(segment); _console.Write(segment);

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console.Tests
{
public sealed class TestableConsoleInput : IAnsiConsoleInput
{
private readonly Queue<ConsoleKeyInfo> _input;
public TestableConsoleInput()
{
_input = new Queue<ConsoleKeyInfo>();
}
public void PushText(string input)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}
foreach (var character in input)
{
PushCharacter(character);
}
PushKey(ConsoleKey.Enter);
}
public void PushCharacter(char character)
{
var control = char.IsUpper(character);
_input.Enqueue(new ConsoleKeyInfo(character, (ConsoleKey)character, false, false, control));
}
public void PushKey(ConsoleKey key)
{
_input.Enqueue(new ConsoleKeyInfo((char)key, key, false, false, false));
}
public ConsoleKeyInfo ReadKey(bool intercept)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
return _input.Dequeue();
}
}
}

View File

@@ -53,7 +53,7 @@ namespace Spectre.Console.Tests.Unit
grid.AddRow("Foo"); grid.AddRow("Foo");
// Then // Then
grid.RowCount.ShouldBe(1); grid.Rows.Count.ShouldBe(1);
} }
[Fact] [Fact]

View File

@@ -77,7 +77,7 @@ namespace Spectre.Console.Tests.Unit
table.AddColumn("Bar", c => c.PadLeft(0).PadRight(0)); table.AddColumn("Bar", c => c.PadLeft(0).PadRight(0));
table.AddRow("Baz", "Qux"); table.AddRow("Baz", "Qux");
table.AddRow(new Text("Corgi"), new Padder(new Panel("Waldo")) table.AddRow(new Text("Corgi"), new Padder(new Panel("Waldo"))
.Padding(2, 1, 2, 1)); .Padding(2, 1));
// When // When
console.Render(new Padder(table) console.Render(new Padder(table)

View File

@@ -316,14 +316,14 @@ namespace Spectre.Console.Tests.Unit
var panel = new Panel(grid) var panel = new Panel(grid)
.Expand().RoundedBorder() .Expand().RoundedBorder()
.BorderStyle(new Style().Foreground(Color.Grey)) .BorderStyle(new Style().Foreground(Color.Grey))
.Header("Short paths ", new Style().Foreground(Color.Grey)); .Header("[grey]Short paths[/]");
// When // When
console.Render(panel); console.Render(panel);
// Then // Then
console.Lines.Count.ShouldBe(4); console.Lines.Count.ShouldBe(4);
console.Lines[0].ShouldBe("╭─Short paths ─────────────────────────────────────────────────────────────────────╮"); console.Lines[0].ShouldBe("╭─Short paths─────────────────────────────────────────────────────────────────────╮");
console.Lines[1].ShouldBe("│ at System.Runtime.CompilerServices.TaskAwaiter. │"); console.Lines[1].ShouldBe("│ at System.Runtime.CompilerServices.TaskAwaiter. │");
console.Lines[2].ShouldBe("│ HandleNonSuccessAndDebuggerNotification(Task task) │"); console.Lines[2].ShouldBe("│ HandleNonSuccessAndDebuggerNotification(Task task) │");
console.Lines[3].ShouldBe("╰──────────────────────────────────────────────────────────────────────────────────╯"); console.Lines[3].ShouldBe("╰──────────────────────────────────────────────────────────────────────────────────╯");

View File

@@ -0,0 +1,126 @@
using System;
using Shouldly;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class PromptTests
{
[Fact]
public void Should_Return_Validation_Error_If_Value_Cannot_Be_Converted()
{
// Given
var console = new PlainConsole();
console.Input.PushText("ninety-nine");
console.Input.PushText("99");
// When
console.Prompt(new TextPrompt<int>("Age?"));
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("Age? ninety-nine");
console.Lines[1].ShouldBe("Invalid input");
console.Lines[2].ShouldBe("Age? 99");
}
[Fact]
public void Should_Chose_Default_Value_If_Nothing_Is_Entered()
{
// Given
var console = new PlainConsole();
console.Input.PushKey(ConsoleKey.Enter);
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
console.Lines.Count.ShouldBe(1);
console.Lines[0].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Banana");
}
[Fact]
public void Should_Return_Error_If_An_Invalid_Choice_Is_Made()
{
// Given
var console = new PlainConsole();
console.Input.PushText("Apple");
console.Input.PushText("Banana");
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
console.Lines.Count.ShouldBe(3);
console.Lines[0].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Apple");
console.Lines[1].ShouldBe("Please select one of the available options");
console.Lines[2].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Banana");
}
[Fact]
public void Should_Accept_Choice_In_List()
{
// Given
var console = new PlainConsole();
console.Input.PushText("Orange");
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange")
.DefaultValue("Banana"));
// Then
console.Lines.Count.ShouldBe(1);
console.Lines[0].ShouldBe("Favorite fruit? [Banana/Orange] (Banana): Orange");
}
[Fact]
public void Should_Return_Error_If_Custom_Validation_Fails()
{
// Given
var console = new PlainConsole();
console.Input.PushText("22");
console.Input.PushText("102");
console.Input.PushText("ABC");
console.Input.PushText("99");
// When
console.Prompt(
new TextPrompt<int>("Guess number:")
.ValidationErrorMessage("Invalid input")
.Validate(age =>
{
if (age < 99)
{
return ValidationResult.Error("Too low");
}
else if (age > 99)
{
return ValidationResult.Error("Too high");
}
return ValidationResult.Success();
}));
// Then
console.Lines.Count.ShouldBe(7);
console.Lines[0].ShouldBe("Guess number: 22");
console.Lines[1].ShouldBe("Too low");
console.Lines[2].ShouldBe("Guess number: 102");
console.Lines[3].ShouldBe("Too high");
console.Lines[4].ShouldBe("Guess number: ABC");
console.Lines[5].ShouldBe("Invalid input");
console.Lines[6].ShouldBe("Guess number: 99");
}
}
}

View File

@@ -19,6 +19,34 @@ namespace Spectre.Console.Tests.Unit
console.Lines[0].ShouldBe("────────────────────────────────────────"); console.Lines[0].ShouldBe("────────────────────────────────────────");
} }
[Fact]
public void Should_Render_Default_Rule_With_Specified_Box()
{
// Given
var console = new PlainConsole(width: 40);
// When
console.Render(new Rule().DoubleBorder());
// Then
console.Lines.Count.ShouldBe(1);
console.Lines[0].ShouldBe("════════════════════════════════════════");
}
[Fact]
public void Should_Render_With_Specified_Box()
{
// Given
var console = new PlainConsole(width: 40);
// When
console.Render(new Rule("Hello World").DoubleBorder());
// Then
console.Lines.Count.ShouldBe(1);
console.Lines[0].ShouldBe("═════════════ Hello World ══════════════");
}
[Fact] [Fact]
public void Should_Render_Default_Rule_With_Title_Centered_By_Default() public void Should_Render_Default_Rule_With_Title_Centered_By_Default()
{ {

View File

@@ -98,7 +98,7 @@ namespace Spectre.Console.Tests.Unit
table.AddRow("Foo"); table.AddRow("Foo");
// Then // Then
table.RowCount.ShouldBe(1); table.Rows.Count.ShouldBe(1);
} }
[Fact] [Fact]

View File

@@ -48,6 +48,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calendars", "..\examples\Ca
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules", "..\examples\Rules\Rules.csproj", "{8622A261-02C6-40CA-9797-E3F01ED87D6B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rules", "..\examples\Rules\Rules.csproj", "{8622A261-02C6-40CA-9797-E3F01ED87D6B}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cursor", "..\examples\Cursor\Cursor.csproj", "{75C608C3-ABB4-4168-A229-7F8250B946D1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Prompt", "..\examples\Prompt\Prompt.csproj", "{6351C70F-F368-46DB-BAED-9B87CCD69353}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -226,6 +230,30 @@ Global
{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x64.Build.0 = Release|Any CPU {8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x64.Build.0 = Release|Any CPU
{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x86.ActiveCfg = Release|Any CPU {8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x86.ActiveCfg = Release|Any CPU
{8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x86.Build.0 = Release|Any CPU {8622A261-02C6-40CA-9797-E3F01ED87D6B}.Release|x86.Build.0 = Release|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Debug|x64.ActiveCfg = Debug|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Debug|x64.Build.0 = Debug|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Debug|x86.ActiveCfg = Debug|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Debug|x86.Build.0 = Debug|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|Any CPU.Build.0 = Release|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x64.ActiveCfg = Release|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x64.Build.0 = Release|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x86.ActiveCfg = Release|Any CPU
{75C608C3-ABB4-4168-A229-7F8250B946D1}.Release|x86.Build.0 = Release|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x64.ActiveCfg = Debug|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x64.Build.0 = Debug|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x86.ActiveCfg = Debug|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Debug|x86.Build.0 = Debug|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|Any CPU.Build.0 = Release|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x64.ActiveCfg = Release|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x64.Build.0 = Release|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x86.ActiveCfg = Release|Any CPU
{6351C70F-F368-46DB-BAED-9B87CCD69353}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -244,6 +272,8 @@ Global
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F} {C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
{57691C7D-683D-46E6-AA4F-57A8C5F65D25} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {57691C7D-683D-46E6-AA4F-57A8C5F65D25} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{8622A261-02C6-40CA-9797-E3F01ED87D6B} = {F0575243-121F-4DEE-9F6B-246E26DC0844} {8622A261-02C6-40CA-9797-E3F01ED87D6B} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{75C608C3-ABB4-4168-A229-7F8250B946D1} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
{6351C70F-F368-46DB-BAED-9B87CCD69353} = {F0575243-121F-4DEE-9F6B-246E26DC0844}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}

View File

@@ -0,0 +1,47 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt to display.</param>
/// <returns>The prompt input result.</returns>
public static T Prompt<T>(IPrompt<T> prompt)
{
if (prompt is null)
{
throw new ArgumentNullException(nameof(prompt));
}
return prompt.Show(Console);
}
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt markup text.</param>
/// <returns>The prompt input result.</returns>
public static T Ask<T>(string prompt)
{
return new TextPrompt<T>(prompt).Show(Console);
}
/// <summary>
/// Displays a prompt with two choices, yes or no.
/// </summary>
/// <param name="prompt">The prompt markup text.</param>
/// <returns><c>true</c> if the user selected "yes", otherwise <c>false</c>.</returns>
public static bool Confirm(string prompt)
{
return new ConfirmationPrompt(prompt).Show(Console);
}
}
}

View File

@@ -27,6 +27,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
public static IAnsiConsole Console => _recorder ?? _console.Value; public static IAnsiConsole Console => _recorder ?? _console.Value;
/// <summary>
/// Gets the <see cref="IAnsiConsoleCursor"/>.
/// </summary>
public static IAnsiConsoleCursor Cursor => _recorder?.Cursor ?? _console.Value.Cursor;
/// <summary> /// <summary>
/// Gets the console's capabilities. /// Gets the console's capabilities.
/// </summary> /// </summary>
@@ -56,7 +61,7 @@ namespace Spectre.Console
/// <returns>An <see cref="IAnsiConsole"/> instance.</returns> /// <returns>An <see cref="IAnsiConsole"/> instance.</returns>
public static IAnsiConsole Create(AnsiConsoleSettings settings) public static IAnsiConsole Create(AnsiConsoleSettings settings)
{ {
return AnsiConsoleBuilder.Build(settings); return BackendBuilder.Build(settings);
} }
} }
} }

View File

@@ -0,0 +1,62 @@
namespace Spectre.Console
{
/// <summary>
/// A prompt that is answered with a yes or no.
/// </summary>
public sealed class ConfirmationPrompt : IPrompt<bool>
{
private readonly string _prompt;
/// <summary>
/// Gets or sets the character that represents "yes".
/// </summary>
public char Yes { get; set; } = 'y';
/// <summary>
/// Gets or sets the character that represents "no".
/// </summary>
public char No { get; set; } = 'n';
/// <summary>
/// Gets or sets the message for invalid choices.
/// </summary>
public string InvalidChoiceMessage { get; set; } = "[red]Please select one of the available options[/]";
/// <summary>
/// Gets or sets a value indicating whether or not
/// choices should be shown.
/// </summary>
public bool ShowChoices { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether or not
/// default values should be shown.
/// </summary>
public bool ShowDefaultValue { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="ConfirmationPrompt"/> class.
/// </summary>
/// <param name="prompt">The prompt markup text.</param>
public ConfirmationPrompt(string prompt)
{
_prompt = prompt ?? throw new System.ArgumentNullException(nameof(prompt));
}
/// <inheritdoc/>
public bool Show(IAnsiConsole console)
{
var prompt = new TextPrompt<char>(_prompt)
.InvalidChoiceMessage(InvalidChoiceMessage)
.ValidationErrorMessage(InvalidChoiceMessage)
.ShowChoices(ShowChoices)
.ShowDefaultValue(ShowDefaultValue)
.DefaultValue(Yes)
.AddChoice(Yes)
.AddChoice(No);
var result = prompt.Show(console);
return result == Yes;
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Spectre.Console
{
/// <summary>
/// Represents cursor direction.
/// </summary>
public enum CursorDirection
{
/// <summary>
/// Up
/// </summary>
Up,
/// <summary>
/// Down
/// </summary>
Down,
/// <summary>
/// Left
/// </summary>
Left,
/// <summary>
/// Right
/// </summary>
Right,
}
}

View File

@@ -0,0 +1,49 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static partial class AnsiConsoleExtensions
{
internal static string ReadLine(this IAnsiConsole console, Style? style, bool secret)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
style ??= Style.Plain;
var result = string.Empty;
while (true)
{
var key = console.Input.ReadKey(true);
if (key.Key == ConsoleKey.Enter)
{
return result;
}
if (key.Key == ConsoleKey.Backspace)
{
if (result.Length > 0)
{
result = result.Substring(0, result.Length - 1);
console.Write("\b \b");
}
continue;
}
result += key.KeyChar.ToString();
if (!char.IsControl(key.KeyChar))
{
console.Write(secret ? "*" : key.KeyChar.ToString(), style);
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsole"/>.
/// </summary>
public static partial class AnsiConsoleExtensions
{
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="console">The console.</param>
/// <param name="prompt">The prompt to display.</param>
/// <returns>The prompt input result.</returns>
public static T Prompt<T>(this IAnsiConsole console, IPrompt<T> prompt)
{
if (prompt is null)
{
throw new ArgumentNullException(nameof(prompt));
}
return prompt.Show(console);
}
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="console">The console.</param>
/// <param name="prompt">The prompt markup text.</param>
/// <returns>The prompt input result.</returns>
public static T Ask<T>(this IAnsiConsole console, string prompt)
{
return new TextPrompt<T>(prompt).Show(console);
}
/// <summary>
/// Displays a prompt with two choices, yes or no.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="prompt">The prompt markup text.</param>
/// <returns><c>true</c> if the user selected "yes", otherwise <c>false</c>.</returns>
public static bool Confirm(this IAnsiConsole console, string prompt)
{
return new ConfirmationPrompt(prompt).Show(console);
}
}
}

View File

@@ -18,6 +18,16 @@ namespace Spectre.Console
return new Recorder(console); return new Recorder(console);
} }
/// <summary>
/// Writes the specified string value to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="text">The text to write.</param>
public static void Write(this IAnsiConsole console, string text)
{
Write(console, text, Style.Plain);
}
/// <summary> /// <summary>
/// Writes the specified string value to the console. /// Writes the specified string value to the console.
/// </summary> /// </summary>
@@ -31,6 +41,11 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(console)); throw new ArgumentNullException(nameof(console));
} }
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
console.Write(new Segment(text, style)); console.Write(new Segment(text, style));
} }
@@ -48,6 +63,16 @@ namespace Spectre.Console
console.Write(Environment.NewLine, Style.Plain); console.Write(Environment.NewLine, Style.Plain);
} }
/// <summary>
/// Writes the specified string value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="console">The console to write to.</param>
/// <param name="text">The text to write.</param>
public static void WriteLine(this IAnsiConsole console, string text)
{
WriteLine(console, text, Style.Plain);
}
/// <summary> /// <summary>
/// Writes the specified string value, followed by the current line terminator, to the console. /// Writes the specified string value, followed by the current line terminator, to the console.
/// </summary> /// </summary>
@@ -61,6 +86,11 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(console)); throw new ArgumentNullException(nameof(console));
} }
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
console.Write(new Segment(text, style)); console.Write(new Segment(text, style));
console.WriteLine(); console.WriteLine();
} }

View File

@@ -96,5 +96,37 @@ namespace Spectre.Console
calendar.HeaderStyle = style ?? Style.Plain; calendar.HeaderStyle = style ?? Style.Plain;
return calendar; return calendar;
} }
/// <summary>
/// Shows the calendar header.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar ShowHeader(this Calendar calendar)
{
if (calendar is null)
{
throw new ArgumentNullException(nameof(calendar));
}
calendar.ShowHeader = true;
return calendar;
}
/// <summary>
/// Hides the calendar header.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar HideHeader(this Calendar calendar)
{
if (calendar is null)
{
throw new ArgumentNullException(nameof(calendar));
}
calendar.ShowHeader = false;
return calendar;
}
} }
} }

View File

@@ -24,5 +24,24 @@ namespace Spectre.Console
obj.NoWrap = true; obj.NoWrap = true;
return obj; return obj;
} }
/// <summary>
/// Sets the width of the column.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IColumn"/>.</typeparam>
/// <param name="obj">The column.</param>
/// <param name="width">The column width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Width<T>(this T obj, int? width)
where T : class, IColumn
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Width = width;
return obj;
}
} }
} }

View File

@@ -0,0 +1,135 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="ConfirmationPrompt"/>.
/// </summary>
public static class ConfirmationPromptExtensions
{
/// <summary>
/// Show or hide choices.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <param name="show">Whether or not the choices should be visible.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt ShowChoices(this ConfirmationPrompt obj, bool show)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.ShowChoices = show;
return obj;
}
/// <summary>
/// Shows choices.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt ShowChoices(this ConfirmationPrompt obj)
{
return ShowChoices(obj, true);
}
/// <summary>
/// Hides choices.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt HideChoices(this ConfirmationPrompt obj)
{
return ShowChoices(obj, false);
}
/// <summary>
/// Show or hide the default value.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <param name="show">Whether or not the default value should be visible.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt ShowDefaultValue(this ConfirmationPrompt obj, bool show)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.ShowDefaultValue = show;
return obj;
}
/// <summary>
/// Shows the default value.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt ShowDefaultValue(this ConfirmationPrompt obj)
{
return ShowDefaultValue(obj, true);
}
/// <summary>
/// Hides the default value.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt HideDefaultValue(this ConfirmationPrompt obj)
{
return ShowDefaultValue(obj, false);
}
/// <summary>
/// Sets the "invalid choice" message for the prompt.
/// </summary>
/// <param name="obj">The prompt.</param>
/// <param name="message">The "invalid choice" message.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt InvalidChoiceMessage(this ConfirmationPrompt obj, string message)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.InvalidChoiceMessage = message;
return obj;
}
/// <summary>
/// Sets the character to interpret as "yes".
/// </summary>
/// <param name="obj">The confirmation prompt.</param>
/// <param name="character">The character to interpret as "yes".</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt Yes(this ConfirmationPrompt obj, char character)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Yes = character;
return obj;
}
/// <summary>
/// Sets the character to interpret as "no".
/// </summary>
/// <param name="obj">The confirmation prompt.</param>
/// <param name="character">The character to interpret as "no".</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static ConfirmationPrompt No(this ConfirmationPrompt obj, char character)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.No = character;
return obj;
}
}
}

View File

@@ -0,0 +1,152 @@
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="IAnsiConsoleCursor"/>.
/// </summary>
public static class CursorExtensions
{
/// <summary>
/// Shows the cursor.
/// </summary>
/// <param name="cursor">The cursor.</param>
public static void Show(this IAnsiConsoleCursor cursor)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Show(true);
}
/// <summary>
/// Hides the cursor.
/// </summary>
/// <param name="cursor">The cursor.</param>
public static void Hide(this IAnsiConsoleCursor cursor)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Show(false);
}
/// <summary>
/// Moves the cursor up.
/// </summary>
/// <param name="cursor">The cursor.</param>
public static void MoveUp(this IAnsiConsoleCursor cursor)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Up, 1);
}
/// <summary>
/// Moves the cursor up.
/// </summary>
/// <param name="cursor">The cursor.</param>
/// <param name="steps">The number of steps to move the cursor.</param>
public static void MoveUp(this IAnsiConsoleCursor cursor, int steps)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Up, steps);
}
/// <summary>
/// Moves the cursor down.
/// </summary>
/// <param name="cursor">The cursor.</param>
public static void MoveDown(this IAnsiConsoleCursor cursor)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Down, 1);
}
/// <summary>
/// Moves the cursor down.
/// </summary>
/// <param name="cursor">The cursor.</param>
/// <param name="steps">The number of steps to move the cursor.</param>
public static void MoveDown(this IAnsiConsoleCursor cursor, int steps)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Down, steps);
}
/// <summary>
/// Moves the cursor to the left.
/// </summary>
/// <param name="cursor">The cursor.</param>
public static void MoveLeft(this IAnsiConsoleCursor cursor)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Left, 1);
}
/// <summary>
/// Moves the cursor to the left.
/// </summary>
/// <param name="cursor">The cursor.</param>
/// <param name="steps">The number of steps to move the cursor.</param>
public static void MoveLeft(this IAnsiConsoleCursor cursor, int steps)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Left, steps);
}
/// <summary>
/// Moves the cursor to the right.
/// </summary>
/// <param name="cursor">The cursor.</param>
public static void MoveRight(this IAnsiConsoleCursor cursor)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Right, 1);
}
/// <summary>
/// Moves the cursor to the right.
/// </summary>
/// <param name="cursor">The cursor.</param>
/// <param name="steps">The number of steps to move the cursor.</param>
public static void MoveRight(this IAnsiConsoleCursor cursor, int steps)
{
if (cursor is null)
{
throw new System.ArgumentNullException(nameof(cursor));
}
cursor.Move(CursorDirection.Right, steps);
}
}
}

View File

@@ -6,6 +6,21 @@ namespace Spectre.Console.Internal
{ {
internal static class EnumerableExtensions internal static class EnumerableExtensions
{ {
public static int GetCount<T>(this IEnumerable<T> source)
{
if (source is IList<T> list)
{
return list.Count;
}
if (source is T[] array)
{
return array.Length;
}
return source.Count();
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{ {
foreach (var item in source) foreach (var item in source)
@@ -54,11 +69,13 @@ namespace Spectre.Console.Internal
return source.Select((value, index) => func(value, index)); return source.Select((value, index) => func(value, index));
} }
#if !NET5_0
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>( public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
this IEnumerable<TFirst> source, IEnumerable<TSecond> first) this IEnumerable<TFirst> source, IEnumerable<TSecond> first)
{ {
return source.Zip(first, (first, second) => (first, second)); return source.Zip(first, (first, second) => (first, second));
} }
#endif
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>( public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third) this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)

View File

@@ -1,4 +1,5 @@
using System; using System;
using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console

View File

@@ -69,8 +69,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(grid)); throw new ArgumentNullException(nameof(grid));
} }
var columns = new IRenderable[grid.ColumnCount]; var columns = new IRenderable[grid.Columns.Count];
Enumerable.Range(0, grid.ColumnCount).ForEach(index => columns[index] = Text.Empty); Enumerable.Range(0, grid.Columns.Count).ForEach(index => columns[index] = Text.Empty);
grid.AddRow(columns); grid.AddRow(columns);
return grid; return grid;

View File

@@ -30,10 +30,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
style ??= panel.Header?.Style;
alignment ??= panel.Header?.Alignment; alignment ??= panel.Header?.Alignment;
return SetHeader(panel, new PanelHeader(text, alignment));
return SetHeader(panel, new PanelHeader(text, style, alignment));
} }
/// <summary> /// <summary>
@@ -54,5 +52,18 @@ namespace Spectre.Console
panel.Header = header; panel.Header = header;
return panel; return panel;
} }
/// <summary>
/// Sets the panel header style.
/// </summary>
/// <param name="panel">The panel.</param>
/// <param name="style">The header style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
[Obsolete("Use markup in header instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static Panel HeaderStyle(this Panel panel, Style style)
{
return panel;
}
} }
} }

View File

@@ -22,7 +22,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(left, obj.Padding.Top, obj.Padding.Right, obj.Padding.Bottom)); return Padding(obj, new Padding(left, obj.Padding.GetTopSafe(), obj.Padding.GetRightSafe(), obj.Padding.GetBottomSafe()));
} }
/// <summary> /// <summary>
@@ -40,7 +40,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(obj.Padding.Left, top, obj.Padding.Right, obj.Padding.Bottom)); return Padding(obj, new Padding(obj.Padding.GetLeftSafe(), top, obj.Padding.GetRightSafe(), obj.Padding.GetBottomSafe()));
} }
/// <summary> /// <summary>
@@ -58,7 +58,7 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(obj.Padding.Left, obj.Padding.Top, right, obj.Padding.Bottom)); return Padding(obj, new Padding(obj.Padding.GetLeftSafe(), obj.Padding.GetTopSafe(), right, obj.Padding.GetBottomSafe()));
} }
/// <summary> /// <summary>
@@ -76,11 +76,11 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
} }
return Padding(obj, new Padding(obj.Padding.Left, obj.Padding.Top, obj.Padding.Right, bottom)); return Padding(obj, new Padding(obj.Padding.GetLeftSafe(), obj.Padding.GetTopSafe(), obj.Padding.GetRightSafe(), bottom));
} }
/// <summary> /// <summary>
/// Sets the left and right padding. /// Sets the left, top, right and bottom padding.
/// </summary> /// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam> /// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param> /// <param name="obj">The paddable object instance.</param>
@@ -95,6 +95,20 @@ namespace Spectre.Console
return Padding(obj, new Padding(left, top, right, bottom)); return Padding(obj, new Padding(left, top, right, bottom));
} }
/// <summary>
/// Sets the horizontal and vertical padding.
/// </summary>
/// <typeparam name="T">An object implementing <see cref="IPaddable"/>.</typeparam>
/// <param name="obj">The paddable object instance.</param>
/// <param name="horizontal">The left and right padding.</param>
/// <param name="vertical">The top and bottom padding.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static T Padding<T>(this T obj, int horizontal, int vertical)
where T : class, IPaddable
{
return Padding(obj, new Padding(horizontal, vertical));
}
/// <summary> /// <summary>
/// Sets the padding. /// Sets the padding.
/// </summary> /// </summary>

View File

@@ -0,0 +1,48 @@
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="Padding"/>.
/// </summary>
public static class PaddingExtensions
{
/// <summary>
/// Gets the left padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The left padding or zero if <c>padding</c> is null.</returns>
public static int GetLeftSafe(this Padding? padding)
{
return padding?.Left ?? 0;
}
/// <summary>
/// Gets the right padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The right padding or zero if <c>padding</c> is null.</returns>
public static int GetRightSafe(this Padding? padding)
{
return padding?.Right ?? 0;
}
/// <summary>
/// Gets the top padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The top padding or zero if <c>padding</c> is null.</returns>
public static int GetTopSafe(this Padding? padding)
{
return padding?.Top ?? 0;
}
/// <summary>
/// Gets the bottom padding.
/// </summary>
/// <param name="padding">The padding.</param>
/// <returns>The bottom padding or zero if <c>padding</c> is null.</returns>
public static int GetBottomSafe(this Padding? padding)
{
return padding?.Bottom ?? 0;
}
}
}

View File

@@ -12,10 +12,9 @@ namespace Spectre.Console
/// </summary> /// </summary>
/// <param name="panel">The panel.</param> /// <param name="panel">The panel.</param>
/// <param name="text">The header text.</param> /// <param name="text">The header text.</param>
/// <param name="style">The header style.</param>
/// <param name="alignment">The header alignment.</param> /// <param name="alignment">The header alignment.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns> /// <returns>The same instance so that multiple calls can be chained.</returns>
public static Panel Header(this Panel panel, string text, Style? style = null, Justify? alignment = null) public static Panel Header(this Panel panel, string text, Justify? alignment = null)
{ {
if (panel is null) if (panel is null)
{ {
@@ -27,42 +26,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(text)); throw new ArgumentNullException(nameof(text));
} }
style ??= panel.Header?.Style;
alignment ??= panel.Header?.Alignment; alignment ??= panel.Header?.Alignment;
return Header(panel, new PanelHeader(text, alignment));
return Header(panel, new PanelHeader(text, style, alignment));
}
/// <summary>
/// Sets the panel header style.
/// </summary>
/// <param name="panel">The panel.</param>
/// <param name="style">The header style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Panel HeaderStyle(this Panel panel, Style style)
{
if (panel is null)
{
throw new ArgumentNullException(nameof(panel));
}
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
if (panel.Header != null)
{
// Update existing style
panel.Header.Style = style;
}
else
{
// Create header
Header(panel, string.Empty, style, null);
}
return panel;
} }
/// <summary> /// <summary>
@@ -86,7 +51,7 @@ namespace Spectre.Console
else else
{ {
// Create header // Create header
Header(panel, string.Empty, null, alignment); Header(panel, string.Empty, alignment);
} }
return panel; return panel;

View File

@@ -1,3 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Spectre.Console.Internal;
using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console
{ {
/// <summary> /// <summary>
@@ -5,6 +13,11 @@ namespace Spectre.Console
/// </summary> /// </summary>
public static class StringExtensions public static class StringExtensions
{ {
// Cache whether or not internally normalized line endings
// already are normalized. No reason to do yet another replace if it is.
private static readonly bool _alreadyNormalized
= Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Escapes text so that it wont be interpreted as markup. /// Escapes text so that it wont be interpreted as markup.
/// </summary> /// </summary>
@@ -18,8 +31,137 @@ namespace Spectre.Console
} }
return text return text
.Replace("[", "[[") .ReplaceExact("[", "[[")
.Replace("]", "]]"); .ReplaceExact("]", "]]");
}
internal static int CellLength(this string text, RenderContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return Cell.GetCellLength(context, text);
}
internal static string Capitalize(this string text, CultureInfo? culture = null)
{
if (text == null)
{
return string.Empty;
}
culture ??= CultureInfo.InvariantCulture;
if (text.Length > 0 && char.IsLower(text[0]))
{
text = string.Format(culture, "{0}{1}", char.ToUpper(text[0], culture), text.Substring(1));
}
return text;
}
internal static string NormalizeLineEndings(this string? text, bool native = false)
{
text = text?.ReplaceExact("\r\n", "\n");
text = text?.ReplaceExact("\r", string.Empty);
text ??= string.Empty;
if (native && !_alreadyNormalized)
{
text = text.ReplaceExact("\n", Environment.NewLine);
}
return text;
}
internal static string[] SplitLines(this string text)
{
var result = text?.NormalizeLineEndings()?.Split(new[] { '\n' }, StringSplitOptions.None);
return result ?? Array.Empty<string>();
}
internal static string[] SplitWords(this string word, StringSplitOptions options = StringSplitOptions.None)
{
var result = new List<string>();
static string Read(StringBuffer reader, Func<char, bool> criteria)
{
var buffer = new StringBuilder();
while (!reader.Eof)
{
var current = reader.Peek();
if (!criteria(current))
{
break;
}
buffer.Append(reader.Read());
}
return buffer.ToString();
}
using (var reader = new StringBuffer(word))
{
while (!reader.Eof)
{
var current = reader.Peek();
if (char.IsWhiteSpace(current))
{
var x = Read(reader, c => char.IsWhiteSpace(c));
if (options != StringSplitOptions.RemoveEmptyEntries)
{
result.Add(x);
}
}
else
{
result.Add(Read(reader, c => !char.IsWhiteSpace(c)));
}
}
}
return result.ToArray();
}
internal static string Repeat(this string text, int count)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
if (count <= 0)
{
return string.Empty;
}
if (count == 1)
{
return text;
}
return string.Concat(Enumerable.Repeat(text, count));
}
internal static string ReplaceExact(this string text, string oldValue, string? newValue)
{
#if NET5_0
return text.Replace(oldValue, newValue, StringComparison.Ordinal);
#else
return text.Replace(oldValue, newValue);
#endif
}
internal static bool ContainsExact(this string text, string value)
{
#if NET5_0
return text.Contains(value, StringComparison.Ordinal);
#else
return text.Contains(value);
#endif
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace Spectre.Console namespace Spectre.Console
{ {
@@ -87,5 +88,26 @@ namespace Spectre.Console
decoration: style.Decoration, decoration: style.Decoration,
link: link); link: link);
} }
internal static Style Combine(this Style style, IEnumerable<Style> source)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
if (source is null)
{
return style;
}
var current = style;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
} }
} }

View File

@@ -36,6 +36,22 @@ namespace Spectre.Console
return table; return table;
} }
/// <summary>
/// Adds a row to the table.
/// </summary>
/// <param name="table">The table to add the row to.</param>
/// <param name="columns">The row columns to add.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Table AddRow(this Table table, params IRenderable[] columns)
{
if (table is null)
{
throw new ArgumentNullException(nameof(table));
}
return table.AddRow(columns);
}
/// <summary> /// <summary>
/// Adds an empty row to the table. /// Adds an empty row to the table.
/// </summary> /// </summary>
@@ -48,8 +64,8 @@ namespace Spectre.Console
throw new ArgumentNullException(nameof(table)); throw new ArgumentNullException(nameof(table));
} }
var columns = new IRenderable[table.ColumnCount]; var columns = new IRenderable[table.Columns.Count];
Enumerable.Range(0, table.ColumnCount).ForEach(index => columns[index] = Text.Empty); Enumerable.Range(0, table.Columns.Count).ForEach(index => columns[index] = Text.Empty);
table.AddRow(columns); table.AddRow(columns);
return table; return table;
} }

View File

@@ -0,0 +1,266 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Contains extension methods for <see cref="TextPrompt{T}"/>.
/// </summary>
public static class TextPromptExtensions
{
/// <summary>
/// Allow empty input.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> AllowEmpty<T>(this TextPrompt<T> obj)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.AllowEmpty = true;
return obj;
}
/// <summary>
/// Sets the prompt style.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="style">The prompt style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> PromptStyle<T>(this TextPrompt<T> obj, Style style)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
obj.PromptStyle = style;
return obj;
}
/// <summary>
/// Show or hide choices.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="show">Whether or not choices should be visible.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> ShowChoices<T>(this TextPrompt<T> obj, bool show)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.ShowChoices = show;
return obj;
}
/// <summary>
/// Shows choices.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> ShowChoices<T>(this TextPrompt<T> obj)
{
return ShowChoices(obj, true);
}
/// <summary>
/// Hides choices.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> HideChoices<T>(this TextPrompt<T> obj)
{
return ShowChoices(obj, false);
}
/// <summary>
/// Show or hide the default value.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="show">Whether or not the default value should be visible.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> ShowDefaultValue<T>(this TextPrompt<T> obj, bool show)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.ShowDefaultValue = show;
return obj;
}
/// <summary>
/// Shows the default value.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> ShowDefaultValue<T>(this TextPrompt<T> obj)
{
return ShowDefaultValue(obj, true);
}
/// <summary>
/// Hides the default value.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> HideDefaultValue<T>(this TextPrompt<T> obj)
{
return ShowDefaultValue(obj, false);
}
/// <summary>
/// Sets the validation error message for the prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="message">The validation error message.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> ValidationErrorMessage<T>(this TextPrompt<T> obj, string message)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.ValidationErrorMessage = message;
return obj;
}
/// <summary>
/// Sets the "invalid choice" message for the prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="message">The "invalid choice" message.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> InvalidChoiceMessage<T>(this TextPrompt<T> obj, string message)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.InvalidChoiceMessage = message;
return obj;
}
/// <summary>
/// Sets the default value of the prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="value">The default value.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> DefaultValue<T>(this TextPrompt<T> obj, T value)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.DefaultValue = new TextPrompt<T>.DefaultValueContainer(value);
return obj;
}
/// <summary>
/// Sets the validation criteria for the prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="validator">The validation criteria.</param>
/// <param name="message">The validation error message.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, bool> validator, string? message = null)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Validator = result =>
{
if (validator(result))
{
return ValidationResult.Success();
}
return ValidationResult.Error(message);
};
return obj;
}
/// <summary>
/// Sets the validation criteria for the prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="validator">The validation criteria.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> Validate<T>(this TextPrompt<T> obj, Func<T, ValidationResult> validator)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Validator = validator;
return obj;
}
/// <summary>
/// Adds a choice to the prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="choice">The choice to add.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> AddChoice<T>(this TextPrompt<T> obj, T choice)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.Choices.Add(choice);
return obj;
}
/// <summary>
/// Replaces prompt user input with asterixes in the console.
/// </summary>
/// <typeparam name="T">The prompt type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> Secret<T>(this TextPrompt<T> obj)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.IsSecret = true;
return obj;
}
}
}

View File

@@ -18,6 +18,16 @@ namespace Spectre.Console
/// </summary> /// </summary>
Encoding Encoding { get; } Encoding Encoding { get; }
/// <summary>
/// Gets the console cursor.
/// </summary>
IAnsiConsoleCursor Cursor { get; }
/// <summary>
/// Gets the console input.
/// </summary>
IAnsiConsoleInput Input { get; }
/// <summary> /// <summary>
/// Gets the buffer width of the console. /// Gets the buffer width of the console.
/// </summary> /// </summary>
@@ -28,6 +38,12 @@ namespace Spectre.Console
/// </summary> /// </summary>
int Height { get; } int Height { get; }
/// <summary>
/// Clears the console.
/// </summary>
/// <param name="home">If the cursor should be moved to the home position.</param>
void Clear(bool home);
/// <summary> /// <summary>
/// Writes a string followed by a line terminator to the console. /// Writes a string followed by a line terminator to the console.
/// </summary> /// </summary>

View File

@@ -0,0 +1,28 @@
namespace Spectre.Console
{
/// <summary>
/// Represents the console's cursor.
/// </summary>
public interface IAnsiConsoleCursor
{
/// <summary>
/// Shows or hides the cursor.
/// </summary>
/// <param name="show"><c>true</c> to show the cursor, <c>false</c> to hide it.</param>
void Show(bool show);
/// <summary>
/// Sets the cursor position.
/// </summary>
/// <param name="column">The column to move the cursor to.</param>
/// <param name="line">The line to move the cursor to.</param>
void SetPosition(int column, int line);
/// <summary>
/// Moves the cursor relative to the current position.
/// </summary>
/// <param name="direction">The direction to move the cursor.</param>
/// <param name="steps">The number of steps to move the cursor.</param>
void Move(CursorDirection direction, int steps);
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents the console's input mechanism.
/// </summary>
public interface IAnsiConsoleInput
{
/// <summary>
/// Reads a key from the console.
/// </summary>
/// <param name="intercept">Whether or not to intercept the key.</param>
/// <returns>The key that was read.</returns>
ConsoleKeyInfo ReadKey(bool intercept);
}
}

View File

@@ -10,5 +10,10 @@ namespace Spectre.Console
/// or not wrapping should be prevented. /// or not wrapping should be prevented.
/// </summary> /// </summary>
bool NoWrap { get; set; } bool NoWrap { get; set; }
/// <summary>
/// Gets or sets the width of the column.
/// </summary>
int? Width { get; set; }
} }
} }

View File

@@ -3,7 +3,7 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Represents something that has a box border. /// Represents something that has a box border.
/// </summary> /// </summary>
public interface IHasBoxBorder : IHasBorder public interface IHasBoxBorder
{ {
/// <summary> /// <summary>
/// Gets or sets the box. /// Gets or sets the box.

View File

@@ -8,6 +8,6 @@ namespace Spectre.Console
/// <summary> /// <summary>
/// Gets or sets the padding. /// Gets or sets the padding.
/// </summary> /// </summary>
public Padding Padding { get; set; } public Padding? Padding { get; set; }
} }
} }

View File

@@ -0,0 +1,16 @@
namespace Spectre.Console
{
/// <summary>
/// Represents a prompt.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
public interface IPrompt<T>
{
/// <summary>
/// Shows the prompt.
/// </summary>
/// <param name="console">The console.</param>
/// <returns>The prompt input result.</returns>
T Show(IAnsiConsole console);
}
}

View File

@@ -5,13 +5,17 @@ using Spectre.Console.Rendering;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {
internal sealed class AnsiConsoleRenderer : IAnsiConsole internal sealed class AnsiBackend : IAnsiConsole
{ {
private readonly TextWriter _out; private readonly TextWriter _out;
private readonly AnsiBuilder _ansiBuilder; private readonly AnsiBuilder _ansiBuilder;
private readonly AnsiCursor _cursor;
private readonly ConsoleInput _input;
public Capabilities Capabilities { get; } public Capabilities Capabilities { get; }
public Encoding Encoding { get; } public Encoding Encoding { get; }
public IAnsiConsoleCursor Cursor => _cursor;
public IAnsiConsoleInput Input => _input;
public int Width public int Width
{ {
@@ -39,7 +43,7 @@ namespace Spectre.Console.Internal
} }
} }
public AnsiConsoleRenderer(TextWriter @out, Capabilities capabilities, ILinkIdentityGenerator? linkHasher) public AnsiBackend(TextWriter @out, Capabilities capabilities, ILinkIdentityGenerator? linkHasher)
{ {
_out = @out ?? throw new ArgumentNullException(nameof(@out)); _out = @out ?? throw new ArgumentNullException(nameof(@out));
@@ -47,6 +51,18 @@ namespace Spectre.Console.Internal
Encoding = _out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8; Encoding = _out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
_ansiBuilder = new AnsiBuilder(Capabilities, linkHasher); _ansiBuilder = new AnsiBuilder(Capabilities, linkHasher);
_cursor = new AnsiCursor(this);
_input = new ConsoleInput();
}
public void Clear(bool home)
{
Write(Segment.Control("\u001b[2J"));
if (home)
{
Cursor.SetPosition(0, 0);
}
} }
public void Write(Segment segment) public void Write(Segment segment)

View File

@@ -0,0 +1,56 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal sealed class AnsiCursor : IAnsiConsoleCursor
{
private readonly AnsiBackend _renderer;
public AnsiCursor(AnsiBackend renderer)
{
_renderer = renderer ?? throw new ArgumentNullException(nameof(renderer));
}
public void Show(bool show)
{
if (show)
{
_renderer.Write(Segment.Control("\u001b[?25h"));
}
else
{
_renderer.Write(Segment.Control("\u001b[?25l"));
}
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
return;
}
switch (direction)
{
case CursorDirection.Up:
_renderer.Write(Segment.Control($"\u001b[{steps}A"));
break;
case CursorDirection.Down:
_renderer.Write(Segment.Control($"\u001b[{steps}B"));
break;
case CursorDirection.Right:
_renderer.Write(Segment.Control($"\u001b[{steps}C"));
break;
case CursorDirection.Left:
_renderer.Write(Segment.Control($"\u001b[{steps}D"));
break;
}
}
public void SetPosition(int column, int line)
{
_renderer.Write(Segment.Control($"\u001b[{line};{column}H"));
}
}
}

View File

@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {
internal static class AnsiConsoleBuilder internal static class BackendBuilder
{ {
public static IAnsiConsole Build(AnsiConsoleSettings settings) public static IAnsiConsole Build(AnsiConsoleSettings settings)
{ {
@@ -60,11 +60,11 @@ namespace Spectre.Console.Internal
// Create the renderer // Create the renderer
if (supportsAnsi) if (supportsAnsi)
{ {
return new AnsiConsoleRenderer(buffer, capabilities, settings.LinkIdentityGenerator); return new AnsiBackend(buffer, capabilities, settings.LinkIdentityGenerator);
} }
else else
{ {
return new FallbackConsoleRenderer(buffer, capabilities); return new FallbackBackend(buffer, capabilities);
} }
} }
} }

View File

@@ -0,0 +1,92 @@
using System;
using System.IO;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal sealed class FallbackBackend : IAnsiConsole
{
private readonly ColorSystem _system;
private readonly FallbackCursor _cursor;
private readonly ConsoleInput _input;
private Style? _lastStyle;
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public IAnsiConsoleCursor Cursor => _cursor;
public IAnsiConsoleInput Input => _input;
public int Width
{
get { return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth); }
}
public int Height
{
get { return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight); }
}
public FallbackBackend(TextWriter @out, Capabilities capabilities)
{
if (capabilities == null)
{
throw new ArgumentNullException(nameof(capabilities));
}
_system = capabilities.ColorSystem;
_cursor = new FallbackCursor();
_input = new ConsoleInput();
if (@out != System.Console.Out)
{
System.Console.SetOut(@out ?? throw new ArgumentNullException(nameof(@out)));
}
Encoding = System.Console.OutputEncoding;
Capabilities = capabilities;
}
public void Clear(bool home)
{
var (x, y) = (System.Console.CursorLeft, System.Console.CursorTop);
System.Console.Clear();
if (!home)
{
// Set the cursor position
System.Console.SetCursorPosition(x, y);
}
}
public void Write(Segment segment)
{
if (_lastStyle?.Equals(segment.Style) != true)
{
SetStyle(segment.Style);
}
System.Console.Write(segment.Text.NormalizeLineEndings(native: true));
}
private void SetStyle(Style style)
{
_lastStyle = style;
System.Console.ResetColor();
var background = Color.ToConsoleColor(style.Background);
if (_system != ColorSystem.NoColors && (int)background != -1)
{
System.Console.BackgroundColor = background;
}
var foreground = Color.ToConsoleColor(style.Foreground);
if (_system != ColorSystem.NoColors && (int)foreground != -1)
{
System.Console.ForegroundColor = foreground;
}
}
}
}

View File

@@ -0,0 +1,40 @@
namespace Spectre.Console.Internal
{
internal sealed class FallbackCursor : IAnsiConsoleCursor
{
public void Show(bool show)
{
System.Console.CursorVisible = show;
}
public void Move(CursorDirection direction, int steps)
{
if (steps == 0)
{
return;
}
switch (direction)
{
case CursorDirection.Up:
System.Console.CursorTop -= steps;
break;
case CursorDirection.Down:
System.Console.CursorTop += steps;
break;
case CursorDirection.Left:
System.Console.CursorLeft -= steps;
break;
case CursorDirection.Right:
System.Console.CursorLeft += steps;
break;
}
}
public void SetPosition(int x, int y)
{
System.Console.CursorLeft = x;
System.Console.CursorTop = y;
}
}
}

View File

@@ -2,9 +2,9 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
namespace Spectre.Console.Internal.Collections namespace Spectre.Console.Internal
{ {
internal sealed class ListWithCallback<T> : IList<T> internal sealed class ListWithCallback<T> : IList<T>, IReadOnlyList<T>
{ {
private readonly List<T> _list; private readonly List<T> _list;
private readonly Action _callback; private readonly Action _callback;

View File

@@ -0,0 +1,17 @@
using System;
namespace Spectre.Console.Internal
{
internal sealed class ConsoleInput : IAnsiConsoleInput
{
public ConsoleKeyInfo ReadKey(bool intercept)
{
if (!Environment.UserInteractive)
{
throw new InvalidOperationException("Failed to read input in non-interactive mode.");
}
return System.Console.ReadKey(intercept);
}
}
}

View File

@@ -1,10 +1,9 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Spectre.Console.Internal;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
namespace Spectre.Console namespace Spectre.Console.Internal
{ {
internal static class ExceptionFormatter internal static class ExceptionFormatter
{ {

View File

@@ -31,7 +31,7 @@ namespace Spectre.Console.Internal
} }
var line = lines.Dequeue(); var line = lines.Dequeue();
line = line.Replace(" ---> ", string.Empty); line = line.ReplaceExact(" ---> ", string.Empty);
var match = _messageRegex.Match(line); var match = _messageRegex.Match(line);
if (!match.Success) if (!match.Success)

View File

@@ -1,127 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal static class StringExtensions
{
// Cache whether or not internally normalized line endings
// already are normalized. No reason to do yet another replace if it is.
private static readonly bool _alreadyNormalized
= Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase);
public static int CellLength(this string text, RenderContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return Cell.GetCellLength(context, text);
}
public static string Capitalize(this string text, CultureInfo? culture = null)
{
if (text == null)
{
return string.Empty;
}
culture ??= CultureInfo.InvariantCulture;
if (text.Length > 0 && char.IsLower(text[0]))
{
text = string.Format(culture, "{0}{1}", char.ToUpper(text[0], culture), text.Substring(1));
}
return text;
}
public static string NormalizeLineEndings(this string text, bool native = false)
{
text ??= string.Empty;
var normalized = text?.Replace("\r\n", "\n")?.Replace("\r", string.Empty) ?? string.Empty;
if (native && !_alreadyNormalized)
{
normalized = normalized.Replace("\n", Environment.NewLine);
}
return normalized;
}
public static string[] SplitLines(this string text)
{
var result = text?.NormalizeLineEndings()?.Split(new[] { '\n' }, StringSplitOptions.None);
return result ?? Array.Empty<string>();
}
public static string[] SplitWords(this string word, StringSplitOptions options = StringSplitOptions.None)
{
var result = new List<string>();
static string Read(StringBuffer reader, Func<char, bool> criteria)
{
var buffer = new StringBuilder();
while (!reader.Eof)
{
var current = reader.Peek();
if (!criteria(current))
{
break;
}
buffer.Append(reader.Read());
}
return buffer.ToString();
}
using (var reader = new StringBuffer(word))
{
while (!reader.Eof)
{
var current = reader.Peek();
if (char.IsWhiteSpace(current))
{
var x = Read(reader, c => char.IsWhiteSpace(c));
if (options != StringSplitOptions.RemoveEmptyEntries)
{
result.Add(x);
}
}
else
{
result.Add(Read(reader, c => !char.IsWhiteSpace(c)));
}
}
}
return result.ToArray();
}
public static string Repeat(this string text, int count)
{
if (text is null)
{
throw new ArgumentNullException(nameof(text));
}
if (count <= 0)
{
return string.Empty;
}
if (count == 1)
{
return text;
}
return string.Concat(Enumerable.Repeat(text, count));
}
}
}

View File

@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class StyleExtensions
{
public static Style Combine(this Style style, IEnumerable<Style> source)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
if (source is null)
{
return style;
}
var current = style;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
}
}

View File

@@ -1,97 +0,0 @@
using System;
using System.IO;
using System.Text;
using Spectre.Console.Rendering;
namespace Spectre.Console.Internal
{
internal sealed class FallbackConsoleRenderer : IAnsiConsole
{
private readonly TextWriter _out;
private readonly ColorSystem _system;
private Style? _lastStyle;
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public int Width
{
get
{
if (_out.IsStandardOut())
{
return ConsoleHelper.GetSafeBufferWidth(Constants.DefaultBufferWidth);
}
return Constants.DefaultBufferWidth;
}
}
public int Height
{
get
{
if (_out.IsStandardOut())
{
return ConsoleHelper.GetSafeBufferHeight(Constants.DefaultBufferHeight);
}
return Constants.DefaultBufferHeight;
}
}
public FallbackConsoleRenderer(TextWriter @out, Capabilities capabilities)
{
if (capabilities == null)
{
throw new ArgumentNullException(nameof(capabilities));
}
_out = @out ?? throw new ArgumentNullException(nameof(@out));
_system = capabilities.ColorSystem;
if (_out.IsStandardOut())
{
Encoding = System.Console.OutputEncoding;
}
else
{
Encoding = Encoding.UTF8;
}
Capabilities = capabilities;
}
public void Write(Segment segment)
{
if (_lastStyle?.Equals(segment.Style) != true)
{
SetStyle(segment.Style);
}
_out.Write(segment.Text.NormalizeLineEndings(native: true));
}
private void SetStyle(Style style)
{
_lastStyle = style;
if (_out.IsStandardOut())
{
System.Console.ResetColor();
var background = Color.ToConsoleColor(style.Background);
if (_system != ColorSystem.NoColors && _out.IsStandardOut() && (int)background != -1)
{
System.Console.BackgroundColor = background;
}
var foreground = Color.ToConsoleColor(style.Foreground);
if (_system != ColorSystem.NoColors && _out.IsStandardOut() && (int)foreground != -1)
{
System.Console.ForegroundColor = foreground;
}
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.CompilerServices;
namespace Spectre.Console.Internal namespace Spectre.Console.Internal
{ {
@@ -23,9 +24,19 @@ namespace Spectre.Console.Internal
unchecked unchecked
{ {
return Math.Abs( return Math.Abs(
link.GetHashCode() + GetLinkHashCode(link) +
_random.Next(0, int.MaxValue)); _random.Next(0, int.MaxValue));
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetLinkHashCode(string link)
{
#if NET5_0
return link.GetHashCode(StringComparison.Ordinal);
#else
return link.GetHashCode();
#endif
}
} }
} }

View File

@@ -147,7 +147,7 @@ namespace Spectre.Console.Internal
error = null; error = null;
hex ??= string.Empty; hex ??= string.Empty;
hex = hex.Replace("#", string.Empty).Trim(); hex = hex.ReplaceExact("#", string.Empty).Trim();
try try
{ {

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