Skip to content

Commit 4464bc4

Browse files
committed
Add interpolated string handler support to ControlBuilder.
Fixes #55.
1 parent 505367d commit 4464bc4

File tree

2 files changed

+136
-3
lines changed

2 files changed

+136
-3
lines changed

src/core/Text/Control/ControlBuilder.cs

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,115 @@ namespace System.Text.Control;
44

55
public sealed class ControlBuilder
66
{
7+
[EditorBrowsable(EditorBrowsableState.Never)]
8+
[InterpolatedStringHandler]
9+
public readonly ref struct PrintInterpolatedStringHandler
10+
{
11+
const int StackBufferSize = 256;
12+
13+
readonly ControlBuilder _builder;
14+
15+
readonly IFormatProvider? _provider;
16+
17+
readonly ICustomFormatter? _formatter;
18+
19+
public PrintInterpolatedStringHandler(
20+
int literalLength,
21+
int formattedCount,
22+
ControlBuilder builder,
23+
IFormatProvider? provider = null)
24+
{
25+
_builder = builder;
26+
_provider = provider;
27+
_formatter = provider is not CultureInfo ?
28+
(ICustomFormatter?)provider?.GetFormat(typeof(ICustomFormatter)) : null;
29+
}
30+
31+
void AppendSpan(ReadOnlySpan<char> span)
32+
{
33+
_ = _builder.Print(span);
34+
}
35+
36+
public void AppendLiteral(string value)
37+
{
38+
AppendSpan(value);
39+
}
40+
41+
[SuppressMessage("Style", "IDE0038")]
42+
public void AppendFormatted<T>(T value, string? format = null)
43+
{
44+
if (_formatter != null)
45+
{
46+
AppendSpan(_formatter.Format(format, value, _provider));
47+
48+
return;
49+
}
50+
51+
// Do not use pattern matching here as it results in boxing.
52+
if (value is IFormattable)
53+
{
54+
if (value is ISpanFormattable)
55+
{
56+
char[]? rented = null;
57+
Span<char> span = stackalloc char[StackBufferSize];
58+
59+
try
60+
{
61+
int written;
62+
63+
// Try to format the value on the stack; fall back to the heap.
64+
while (!((ISpanFormattable)value).TryFormat(span, out written, format, _provider))
65+
{
66+
if (rented != null)
67+
ArrayPool<char>.Shared.Return(rented);
68+
69+
var len = span.Length * 2;
70+
71+
rented = ArrayPool<char>.Shared.Rent(len);
72+
span = rented.AsSpan(0, len);
73+
}
74+
75+
AppendSpan(span[..written]);
76+
}
77+
finally
78+
{
79+
if (rented != null)
80+
ArrayPool<char>.Shared.Return(rented);
81+
}
82+
}
83+
else
84+
AppendSpan(((IFormattable)value).ToString(format, _provider));
85+
}
86+
else
87+
AppendSpan(value?.ToString());
88+
}
89+
90+
public void AppendFormatted(object? value, string? format = null)
91+
{
92+
// This overload is used when a target-typed expression cannot use the generic overload.
93+
AppendFormatted<object?>(value, format);
94+
}
95+
96+
public void AppendFormatted(string? value)
97+
{
98+
// This overload exists to disambiguate string since it can implicitly convert to both object and
99+
// ReadOnlySpan<char>.
100+
AppendFormatted<string?>(value);
101+
}
102+
103+
public unsafe void AppendFormatted(void* value, string? format = null)
104+
{
105+
// This overload makes pointer values work in interpolation holes; they cannot be passed as generic type
106+
// arguments currently.
107+
AppendFormatted((nuint)value, format);
108+
}
109+
110+
public void AppendFormatted(ReadOnlySpan<char> value)
111+
{
112+
AppendSpan(value);
113+
}
114+
}
115+
7116
const int StackBufferSize = 32;
8117

9118
public ReadOnlySpan<char> Span => _writer.WrittenSpan;
@@ -47,14 +156,38 @@ public ControlBuilder Print<T>(T value)
47156
return Print((value?.ToString()).AsSpan());
48157
}
49158

159+
public ControlBuilder Print([InterpolatedStringHandlerArgument("")] ref PrintInterpolatedStringHandler handler)
160+
{
161+
return this;
162+
}
163+
164+
public ControlBuilder Print(
165+
IFormatProvider? provider,
166+
[InterpolatedStringHandlerArgument("", "provider")] ref PrintInterpolatedStringHandler handler)
167+
{
168+
return this;
169+
}
170+
50171
public ControlBuilder PrintLine()
51172
{
52-
return PrintLine(string.Empty);
173+
return Print(Environment.NewLine);
53174
}
54175

55176
public ControlBuilder PrintLine<T>(T value)
56177
{
57-
return Print(value?.ToString() + Environment.NewLine);
178+
return Print(value).PrintLine();
179+
}
180+
181+
public ControlBuilder PrintLine([InterpolatedStringHandlerArgument("")] ref PrintInterpolatedStringHandler handler)
182+
{
183+
return PrintLine();
184+
}
185+
186+
public ControlBuilder PrintLine(
187+
IFormatProvider? provider,
188+
[InterpolatedStringHandlerArgument("", "provider")] ref PrintInterpolatedStringHandler handler)
189+
{
190+
return PrintLine();
58191
}
59192

60193
// Keep methods in sync with the ControlSequences class.

src/extensions/Logging/Terminal/TerminalLoggerOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Decorator Decorate(byte r, byte g, byte b)
9393
_ = cb.Print("[");
9494

9595
using (_ = Decorate(127, 127, 127))
96-
_ = cb.Print(entry.Timestamp.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture));
96+
_ = cb.Print(CultureInfo.InvariantCulture, $"{entry.Timestamp:HH:mm:ss.fff}");
9797

9898
_ = cb.Print("][");
9999

0 commit comments

Comments
 (0)