Skip to content

Commit 2d7b7c4

Browse files
authored
Merge pull request #774 from jonsequitur/rendering-improvements
Rendering improvements
2 parents dde07c5 + 509cd9a commit 2d7b7c4

29 files changed

+391
-147
lines changed

samples/RenderingPlayground/Program.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,36 @@ public static void Main(
4343
height ?? Console.WindowHeight,
4444
overwrite);
4545

46+
var console = invocationContext.Console;
47+
4648
if (overwrite &&
47-
invocationContext.Console is ITerminal terminal)
49+
console is ITerminal terminal)
4850
{
4951
terminal.Clear();
5052
}
5153

5254
var consoleRenderer = new ConsoleRenderer(
53-
invocationContext.Console,
55+
console,
5456
mode: invocationContext.BindingContext.OutputMode(),
5557
resetAfterRender: true);
5658

5759
switch (sample)
5860
{
5961
case SampleName.Colors:
6062
{
61-
var screen = new ScreenView(renderer: consoleRenderer, invocationContext.Console);
63+
var screen = new ScreenView(renderer: consoleRenderer, console);
6264
screen.Child = new ColorsView(text ?? "*");
6365

6466
screen.Render(region);
6567
}
6668
break;
6769

6870
case SampleName.Dir:
69-
var directoryTableView = new DirectoryTableView(new DirectoryInfo(Directory.GetCurrentDirectory()));
70-
directoryTableView.Render(consoleRenderer, region);
71+
72+
var directoryTableView = new DirectoryTableView(
73+
new DirectoryInfo(Directory.GetCurrentDirectory()));
74+
75+
console.Append(directoryTableView);
7176

7277
break;
7378

@@ -94,14 +99,14 @@ public static void Main(
9499
table.AddColumn(process => $"{process.ProcessName} ", "Name");
95100
table.AddColumn(process => ContentView.FromObservable(process.TrackCpuUsage(), x => $"{x.UsageTotal:P}"), "CPU", ColumnDefinition.Star(1));
96101

97-
var screen = new ScreenView(renderer: consoleRenderer, invocationContext.Console) { Child = table };
102+
var screen = new ScreenView(renderer: consoleRenderer, console) { Child = table };
98103
screen.Render();
99104
}
100105
break;
101106

102107
case SampleName.Clock:
103108
{
104-
var screen = new ScreenView(renderer: consoleRenderer, invocationContext.Console);
109+
var screen = new ScreenView(renderer: consoleRenderer, console);
105110
var lastTime = DateTime.Now;
106111
var clockObservable = new BehaviorSubject<DateTime>(lastTime);
107112
var clockView = ContentView.FromObservable(clockObservable, x => $"{x:T}");
@@ -121,7 +126,7 @@ public static void Main(
121126

122127
case SampleName.GridLayout:
123128
{
124-
var screen = new ScreenView(renderer: consoleRenderer, invocationContext.Console);
129+
var screen = new ScreenView(renderer: consoleRenderer, console);
125130
var content = new ContentView(
126131
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum for Kevin.");
127132
var smallContent = new ContentView("Kevin Bost");
@@ -159,7 +164,7 @@ public static void Main(
159164
}
160165
else
161166
{
162-
var screen = new ScreenView(renderer: consoleRenderer, invocationContext.Console);
167+
var screen = new ScreenView(renderer: consoleRenderer, console);
163168
var stackLayout = new StackLayoutView();
164169
var content1 = new ContentView("Hello World!");
165170
var content2 = new ContentView(

src/System.CommandLine.Rendering.Tests/FormatSpanTests.cs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ public class FormatSpanTests
1111
[Fact]
1212
public void ForegroundColorSpans_with_equivalent_content_have_the_same_hash_code()
1313
{
14-
var one = new ForegroundColorSpan("green");
15-
var two = new ForegroundColorSpan("green");
14+
var one = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
15+
var two = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
1616

1717
one.GetHashCode().Should().Be(two.GetHashCode());
1818
}
1919

2020
[Fact]
2121
public void ForegroundColorSpans_with_the_same_name_are_equal()
2222
{
23-
var one = new ForegroundColorSpan("green");
24-
var two = new ForegroundColorSpan("green");
23+
var one = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
24+
var two = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
2525

2626
one.Equals(two)
2727
.Should()
@@ -35,8 +35,8 @@ public void ForegroundColorSpans_with_the_same_name_are_equal()
3535
[Fact]
3636
public void ForegroundColorSpans_with_different_names_are_not_equal()
3737
{
38-
var one = new ForegroundColorSpan("red");
39-
var two = new ForegroundColorSpan("green");
38+
var one = new ForegroundColorSpan("red", Ansi.Color.Foreground.Green);
39+
var two = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
4040

4141
one.Equals(two)
4242
.Should()
@@ -46,17 +46,17 @@ public void ForegroundColorSpans_with_different_names_are_not_equal()
4646
[Fact]
4747
public void BackgroundColorSpans_with_equivalent_content_have_the_same_hash_code()
4848
{
49-
var one = new BackgroundColorSpan("green");
50-
var two = new BackgroundColorSpan("green");
49+
var one = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
50+
var two = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
5151

5252
one.GetHashCode().Should().Be(two.GetHashCode());
5353
}
5454

5555
[Fact]
5656
public void BackgroundColorSpans_with_the_same_name_are_equal()
5757
{
58-
var one = new BackgroundColorSpan("green");
59-
var two = new BackgroundColorSpan("green");
58+
var one = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
59+
var two = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
6060

6161
one.Equals(two)
6262
.Should()
@@ -70,8 +70,8 @@ public void BackgroundColorSpans_with_the_same_name_are_equal()
7070
[Fact]
7171
public void BackgroundColorSpans_with_different_names_are_not_equal()
7272
{
73-
var one = new BackgroundColorSpan("red");
74-
var two = new BackgroundColorSpan("green");
73+
var one = new BackgroundColorSpan("red", Ansi.Color.Foreground.Red);
74+
var two = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
7575

7676
one.Equals(two)
7777
.Should()
@@ -81,8 +81,8 @@ public void BackgroundColorSpans_with_different_names_are_not_equal()
8181
[Fact]
8282
public void A_ForegroundColorSpan_and_a_BackgroundColorSpan_having_the_same_name_are_not_equal()
8383
{
84-
var one = new ForegroundColorSpan("green");
85-
var two = new BackgroundColorSpan("green");
84+
var one = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
85+
var two = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
8686

8787
one.Equals(two)
8888
.Should()
@@ -92,26 +92,26 @@ public void A_ForegroundColorSpan_and_a_BackgroundColorSpan_having_the_same_name
9292
[Fact]
9393
public void A_ForegroundColorSpan_and_a_BackgroundColorSpan_having_the_same_name_do_not_have_the_same_hash_code()
9494
{
95-
var one = new ForegroundColorSpan("green");
96-
var two = new BackgroundColorSpan("green");
95+
var one = new ForegroundColorSpan("green", Ansi.Color.Foreground.Green);
96+
var two = new BackgroundColorSpan("green", Ansi.Color.Foreground.Green);
9797

9898
one.GetHashCode().Should().NotBe(two.GetHashCode());
9999
}
100100

101101
[Fact]
102102
public void StyleSpans_with_equivalent_content_have_the_same_hash_code()
103103
{
104-
var one = new StyleSpan("green");
105-
var two = new StyleSpan("green");
104+
var one = new StyleSpan("green", Ansi.Color.Foreground.Green);
105+
var two = new StyleSpan("green", Ansi.Color.Foreground.Green);
106106

107107
one.GetHashCode().Should().Be(two.GetHashCode());
108108
}
109109

110110
[Fact]
111111
public void StyleSpans_with_the_same_name_are_equal()
112112
{
113-
var one = new StyleSpan("green");
114-
var two = new StyleSpan("green");
113+
var one = new StyleSpan("green", Ansi.Color.Foreground.Green);
114+
var two = new StyleSpan("green", Ansi.Color.Foreground.Green);
115115

116116
one.Equals(two)
117117
.Should()
@@ -125,8 +125,8 @@ public void StyleSpans_with_the_same_name_are_equal()
125125
[Fact]
126126
public void StyleSpans_with_different_names_are_not_equal()
127127
{
128-
var one = new StyleSpan("red");
129-
var two = new StyleSpan("green");
128+
var one = new StyleSpan("red", Ansi.Color.Foreground.Green);
129+
var two = new StyleSpan("green", Ansi.Color.Foreground.Green);
130130

131131
one.Equals(two)
132132
.Should()

src/System.CommandLine.Rendering.Tests/SpanTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,27 @@ public void Span_starts_update_when_parent_is_added_to_another_parent_span()
8282
innerContainerSpan[3].Start.Should().Be("firstsecond".Length);
8383
innerContainerSpan[4].Start.Should().Be("firstsecondthird".Length);
8484
}
85+
86+
[Fact]
87+
public void ToString_with_non_ansi_omits_ANSI_codes()
88+
{
89+
var span = new SpanFormatter()
90+
.ParseToSpan($"one{ForegroundColorSpan.Red()}two{ForegroundColorSpan.Reset()}three");
91+
92+
span.ToString(OutputMode.NonAnsi)
93+
.Should()
94+
.Be("onetwothree");
95+
}
96+
97+
[Fact]
98+
public void ToString_with_ansi_includes_ANSI_codes()
99+
{
100+
var span = new SpanFormatter()
101+
.ParseToSpan($"one{ForegroundColorSpan.Red()}two{ForegroundColorSpan.Reset()}three");
102+
103+
span.ToString(OutputMode.Ansi)
104+
.Should()
105+
.Be($"one{Ansi.Color.Foreground.Red.EscapeSequence}two{Ansi.Color.Foreground.Default.EscapeSequence}three");
106+
}
85107
}
86108
}

src/System.CommandLine.Rendering.Tests/System.CommandLine.Rendering.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
<Compile Include="..\System.CommandLine.Rendering\WrappingExtensions.cs" Link="WrappingExtensions.cs" />
1717
</ItemGroup>
1818

19+
<ItemGroup>
20+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
21+
</ItemGroup>
22+
1923
<ItemGroup>
2024
<ProjectReference Include="..\System.CommandLine.Rendering\System.CommandLine.Rendering.csproj" />
2125
<ProjectReference Include="..\System.CommandLine.Tests\System.CommandLine.Tests.csproj" />

src/System.CommandLine.Rendering.Tests/TestTerminalTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public void When_in_ANSI_mode_and_ANSI_sequences_are_used_to_set_cursor_position
4848
{
4949
var terminal = (TestTerminal)GetTerminal();
5050

51-
terminal.IsVirtualTerminalModeEnabled = true;
51+
terminal.IsAnsiTerminal = true;
5252

5353
terminal.Out.Write($"before move{Ansi.Cursor.Move.ToLocation(3, 5).EscapeSequence}after move");
5454

@@ -65,7 +65,7 @@ public void When_not_in_ANSI_mode_and_ANSI_sequences_are_used_to_set_cursor_posi
6565
{
6666
var terminal = (TestTerminal)GetTerminal();
6767

68-
terminal.IsVirtualTerminalModeEnabled = false;
68+
terminal.IsAnsiTerminal = false;
6969

7070
var stringWithEscapeSequence = $"before move{Ansi.Cursor.Move.ToLocation(3, 5).EscapeSequence}after move";
7171

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.CommandLine.Builder;
5+
using System.CommandLine.Invocation;
6+
using System.CommandLine.Parsing;
47
using System.CommandLine.Rendering.Views;
58
using System.CommandLine.Tests;
69
using System.Drawing;
710
using FluentAssertions;
11+
using Microsoft.Extensions.DependencyInjection;
812
using Xunit;
913

1014
namespace System.CommandLine.Rendering.Tests
@@ -13,36 +17,73 @@ public class ViewRenderingTests
1317
{
1418
private readonly TestTerminal _terminal = new TestTerminal();
1519

16-
[Fact(Skip = "WIP")]
17-
public void In_NonAnsi_mode_ConsoleView_keeps_track_of_position_so_that_multiple_WriteLine_statements_do_not_overwrite_the_target_region()
20+
[Fact]
21+
public void Views_can_be_registered_for_specific_types()
1822
{
19-
var renderer = new ConsoleRenderer(
20-
_terminal,
21-
OutputMode.NonAnsi);
23+
ParseResult parseResult = null;
24+
25+
var command = new RootCommand
26+
{
27+
Handler = CommandHandler.Create<ParseResult, IConsole>(
28+
(r, c) =>
29+
{
30+
parseResult = r;
31+
c.Append(new ParseResultView(r));
32+
})
33+
};
34+
35+
var parser = new CommandLineBuilder(command)
36+
.UseMiddleware(c =>
37+
{
38+
c.BindingContext
39+
.AddService(
40+
s => new ParseResultView(s.GetService<ParseResult>()));
41+
})
42+
.Build();
43+
44+
var terminal = new TestTerminal
45+
{
46+
IsAnsiTerminal = false
47+
};
48+
49+
parser.Invoke("", terminal);
50+
51+
terminal.Out.ToString().Should().Contain(parseResult.Diagram());
52+
}
2253

23-
var view = new StringsView(new[] {
54+
[Theory]
55+
[InlineData(OutputMode.NonAnsi)]
56+
[InlineData(OutputMode.Ansi)]
57+
public void Views_can_be_appended_to_output(OutputMode outputMode)
58+
{
59+
var view = new StringsView(new[]
60+
{
2461
"1",
2562
"2",
2663
"3"
2764
});
2865

29-
view.Render(renderer, new Region(3, 5, 1, 3));
66+
_terminal.Append(view, outputMode);
3067

3168
_terminal.RenderOperations()
32-
.Should()
33-
.BeEquivalentSequenceTo(new TextRendered("1", new Point(3, 5)),
34-
new TextRendered("2", new Point(3, 6)),
35-
new TextRendered("3", new Point(3, 7)));
69+
.Should()
70+
.BeEquivalentSequenceTo(
71+
new TextRendered("1", new Point(0, 0)),
72+
new TextRendered("2", new Point(0, 1)),
73+
new TextRendered("3" + Environment.NewLine, new Point(0, 2)));
3674
}
3775

38-
[Fact(Skip = "WIP")]
39-
public void In_Ansi_mode_ConsoleView_keeps_track_of_position_so_that_multiple_WriteLine_statements_do_not_overwrite_the_target_region()
76+
[Theory]
77+
[InlineData(OutputMode.NonAnsi)]
78+
[InlineData(OutputMode.Ansi)]
79+
public void ConsoleView_keeps_track_of_position_so_that_multiple_WriteLine_statements_do_not_overwrite_the_target_region(OutputMode outputMode)
4080
{
4181
var renderer = new ConsoleRenderer(
4282
_terminal,
43-
OutputMode.Ansi);
44-
45-
var view = new StringsView(new[] {
83+
outputMode);
84+
85+
var view = new StringsView(new[]
86+
{
4687
"1",
4788
"2",
4889
"3"
@@ -51,22 +92,29 @@ public void In_Ansi_mode_ConsoleView_keeps_track_of_position_so_that_multiple_Wr
5192
view.Render(renderer, new Region(3, 5, 1, 3));
5293

5394
_terminal.RenderOperations()
54-
.Should()
55-
.BeEquivalentSequenceTo(
56-
new TextRendered("1", new Point(3, 5)),
57-
new TextRendered("2", new Point(3, 6)),
58-
new TextRendered("3", new Point(3, 7)));
95+
.Should()
96+
.BeEquivalentSequenceTo(
97+
new TextRendered("1", new Point(3, 5)),
98+
new TextRendered("2", new Point(3, 6)),
99+
new TextRendered("3", new Point(3, 7)));
59100
}
60101

61102
private class StringsView : StackLayoutView
62103
{
63104
public StringsView(string[] strings)
64105
{
65-
foreach(var @string in strings)
106+
foreach (var @string in strings)
66107
{
67108
Add(new ContentView(@string));
68109
}
69110
}
70111
}
71112
}
72-
}
113+
114+
public class ParseResultView : ContentView<ParseResult>
115+
{
116+
public ParseResultView(ParseResult value) : base(value)
117+
{
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)