diff --git a/.agents/skills/pretty-console-expert/SKILL.md b/.agents/skills/pretty-console-expert/SKILL.md new file mode 100644 index 0000000..900c4f6 --- /dev/null +++ b/.agents/skills/pretty-console-expert/SKILL.md @@ -0,0 +1,92 @@ +--- +name: pretty-console-expert +description: Expert workflow for using PrettyConsole correctly and efficiently in C# console apps. Use when tasks involve console styling, colored output, regular prints, prompts, typed input parsing, confirmation prompts, menu/table rendering, overwrite-based rendering, progress bars, spinners, OutputPipe routing, or migration from Spectre.Console/manual ANSI/older PrettyConsole APIs. +--- + +# PrettyConsole Expert + +## Core Workflow + +1. Verify the installed PrettyConsole version before coding. +- Read `Directory.Packages.props`, `*.csproj`, and/or run `dotnet list package`. +- Keep implementation compatible with the installed version; do not "fix" compilation by downgrading unless the user explicitly requests downgrading. + +2. Bring extension APIs into scope: + +```csharp +using PrettyConsole; +using static System.Console; // optional +``` + +3. Choose APIs by intent. +- Styled output: `Console.WriteInterpolated`, `Console.WriteLineInterpolated`. +- Inputs/prompts: `Console.TryReadLine`, `Console.ReadLine`, `Console.Confirm`, `Console.RequestAnyInput`. +- Dynamic rendering: `Console.Overwrite`, `Console.ClearNextLines`, `Console.SkipLines`. +- Progress UI: `ProgressBar.Update`, `ProgressBar.Render`, `Spinner.RunAsync`. +- Menus/tables: `Console.Selection`, `Console.MultiSelection`, `Console.TreeMenu`, `Console.Table`. +- Low-level override only: use `Console.Write(...)` / `Console.WriteLine(...)` span+`ISpanFormattable` overloads only when you intentionally bypass the handler for a custom formatting pipeline. + +## Handler Special Formats + +- Use `:duration` with `TimeSpan` to render compact elapsed time text from the handler: + `Console.WriteInterpolated($"Elapsed {elapsed:duration}")` -> `Elapsed 12h 5m 33s` +- Use `:bytes` with `double` to render human-readable file sizes from the handler: + `Console.WriteInterpolated($"Transferred {bytes:bytes}")` -> `Transferred 12.3 MB` +- Interpolation holes accept `ReadOnlySpan` directly and prefer `ISpanFormattable`, so slices and span-format-capable values stay on the high-performance handler path without dropping to low-level `Write(ReadOnlySpan)` APIs. +- Prefer these formats in status/progress output instead of manual formatting logic. + +## Performance Rules + +- Prefer interpolated-handler APIs over manually concatenated strings. +- Avoid span/formattable `Write`/`WriteLine` overloads in normal app code; reserve them for rare advanced/manual formatting scenarios. +- Keep ANSI/decorations inside interpolation holes (for example, `$"{Markup.Bold}..."`) instead of literal escape codes inside string literals. +- Route transient UI (spinner/progress/overwrite loops) to `OutputPipe.Error` to keep stdout pipe-friendly. +- For very frequent concurrent status updates, prefer a single-reader bounded `Channel` that owns `Console.Overwrite(...)`; let workers `TryWrite` snapshots into a capacity-1 channel with `FullMode = DropWrite` so producers stay non-blocking and intermediate frames can be skipped. +- After the last overwrite/progress frame, clear the UI region once with `Console.ClearNextLines(totalLines, pipe)` or intentionally keep it with `Console.SkipLines`. + +## Practical Patterns + +- For wizard-like flows, wrap `Console.Selection(...)` / `Console.MultiSelection(...)` in retrying `Console.Overwrite(...)` loops so each step reuses one screen region instead of scrolling. +- Prefer `Console.Overwrite(state, static ...)` for fixed-height live regions such as `status + progress`; it avoids closure captures and keeps the rendered surface explicit through `lines`. +- For dynamic spinner headers tied to concurrent work, keep the mutable step/progress state outside the spinner and read it with `Volatile.Read` / `Interlocked` inside the handler factory. + +## API Guardrails (Current Surface) + +- Use `Spinner`, not `IndeterminateProgressBar`. +- Use `Pattern`, not `AnimationSequence`. +- Use `ProgressBar.Render(...)`, not `ProgressBar.WriteProgressBar(...)`. +- Use `ConsoleContext`, not `PrettyConsoleExtensions`. +- Use `ConsoleColor` helpers/tuples (for example `ConsoleColor.Red / ConsoleColor.White`), not removed `ColoredOutput`/`Color` types. +- Use `Confirm(ReadOnlySpan trueValues, ref PrettyConsoleInterpolatedStringHandler handler, bool emptyIsTrue = true)` (boolean parameter is last). +- Use handler factory overloads for dynamic spinner/progress headers: + `(builder, out handler) => handler = builder.Build(OutputPipe.Error, $"...")`. + +## Fast Templates + +```csharp +// Colored/status output +Console.WriteLineInterpolated($"{ConsoleColor.Green / ConsoleColor.DefaultBackground}OK{ConsoleColor.Default}"); + +// Typed input +if (!Console.TryReadLine(out int port, $"Port ({ConsoleColor.Cyan}5000{ConsoleColor.Default}): ")) + port = 5000; + +// Confirm with custom truthy tokens +bool deploy = Console.Confirm(["y", "yes", "deploy"], $"Deploy now? ", emptyIsTrue: false); + +// Spinner +var spinner = new Spinner(); +await spinner.RunAsync(workTask, (builder, out handler) => + handler = builder.Build(OutputPipe.Error, $"Syncing...")); + +// Progress rendering +var bar = new ProgressBar { ProgressColor = ConsoleColor.Green }; +bar.Update(65, "Downloading", sameLine: true); +ProgressBar.Render(OutputPipe.Error, 65, ConsoleColor.Green); +``` + +## Reference File + +Read [references/v5-api-map.md](references/v5-api-map.md) when you need exact usage snippets, migration mapping from old APIs, or a compile-fix checklist. + +If public API usage changes in the edited project, ask whether to update `README.md` and changelog/release-notes files. diff --git a/.agents/skills/pretty-console-expert/agents/openai.yaml b/.agents/skills/pretty-console-expert/agents/openai.yaml new file mode 100644 index 0000000..98b4e8b --- /dev/null +++ b/.agents/skills/pretty-console-expert/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "PrettyConsole Expert" + short_description: "Use PrettyConsole APIs correctly for fast console UIs" + default_prompt: "Implement PrettyConsole features with current APIs, migration-safe names, and allocation-conscious patterns." diff --git a/.agents/skills/pretty-console-expert/references/v5-api-map.md b/.agents/skills/pretty-console-expert/references/v5-api-map.md new file mode 100644 index 0000000..7d05a02 --- /dev/null +++ b/.agents/skills/pretty-console-expert/references/v5-api-map.md @@ -0,0 +1,209 @@ +# PrettyConsole v5 API Map + +Use this file when implementing or reviewing PrettyConsole usage so code compiles against modern APIs and keeps allocation-conscious patterns. + +## 1. Version First + +Read installed version before coding: + +```bash +dotnet list package +rg -n "PrettyConsole" --glob "*.csproj" . +# optionally also check central package management if present: +# rg -n "PrettyConsole" Directory.Packages.props +``` + +If version and request conflict, keep the installed version and adapt code accordingly. + +## 2. Namespace and Setup + +```csharp +using PrettyConsole; +using static System.Console; // optional +``` + +PrettyConsole methods are extension members on `System.Console`. + +## 3. Correct Modern APIs + +- Styled writes: + - `Console.WriteInterpolated(...)` + - `Console.WriteLineInterpolated(...)` +- Inputs: + - `Console.TryReadLine(...)` + - `Console.ReadLine(...)` + - `Console.Confirm(...)` + - `Console.RequestAnyInput(...)` +- Rendering: + - `Console.Overwrite(...)` + - `Console.ClearNextLines(...)` + - `Console.SkipLines(...)` +- Progress: + - `ProgressBar.Update(...)` + - `ProgressBar.Render(...)` + - `Spinner.RunAsync(...)` +- Menus/tables: + - `Console.Selection(...)` + - `Console.MultiSelection(...)` + - `Console.TreeMenu(...)` + - `Console.Table(...)` + +### Interpolated-handler special formats + +- `TimeSpan` with `:duration`: + - `Console.WriteInterpolated($"Elapsed {elapsed:duration}")` + - Example output: `Elapsed 5h 32m 12s` +- `double` with `:bytes`: + - `Console.WriteInterpolated($"Downloaded {size:bytes}")` + - Example output: `Downloaded 12.3 MB` +- `ReadOnlySpan` and `ISpanFormattable` values work directly in interpolation holes: + + ```csharp + ReadOnlySpan prefix = "artifact:".AsSpan()[..8]; + int count = 42; + Console.WriteInterpolated($"{prefix} {count:D4}"); + ``` + + Prefer this over dropping to low-level span `Write(...)` APIs when you still want normal interpolated output composition. + +### Low-level escape hatch (rare) + +Use these only when intentionally bypassing the interpolated handler for a custom formatting pipeline: + +- `Console.Write(...) where T : ISpanFormattable` +- `Console.Write(ReadOnlySpan ...)` +- `Console.WriteLine(...)` + +## 4. Old -> New Migration Table + +- `IndeterminateProgressBar` -> `Spinner` +- `AnimationSequence` -> `Pattern` +- `ProgressBar.WriteProgressBar` -> `ProgressBar.Render` +- `PrettyConsoleExtensions` -> `ConsoleContext` +- Legacy `ColoredOutput`/`Color` types -> `ConsoleColor` helpers and tuples + +## 5. Compile-Safe Patterns + +### Styled output + +```csharp +Console.WriteInterpolated($"[{ConsoleColor.Cyan}info{ConsoleColor.Default}] {message}"); +Console.WriteLineInterpolated(OutputPipe.Error, $"{ConsoleColor.Yellow}warn{ConsoleColor.Default}"); +``` + +### Typed input + +```csharp +if (!Console.TryReadLine(out int port, $"Port ({ConsoleColor.Green}5000{ConsoleColor.Default}): ")) + port = 5000; +``` + +### Confirmation + +```csharp +bool yes = Console.Confirm(["y", "yes"], $"Continue? ", emptyIsTrue: false); +``` + +### Wizard-style menus + +```csharp +static string PromptSelection(string title, string[] options) { + string selection = string.Empty; + + while (selection.Length == 0) { + Console.Overwrite(() => { + selection = Console.Selection(options, $"{ConsoleColor.Cyan}{title}{ConsoleColor.DefaultForeground}:"); + if (selection.Length == 0) + Console.WriteLineInterpolated(OutputPipe.Error, $"{ConsoleColor.Red}Invalid choice."); + }, lines: options.Length + 3, pipe: OutputPipe.Out); + } + + return selection; +} +``` + +Use this when you want multi-step prompts to behave like page transitions instead of adding scrollback on each retry. + +### Spinner with shared progress state + +```csharp +string[] steps = ["Restore", "Compile", "Pack"]; +var step = 0; + +var workTask = Task.Run(async () => { + for (; step < steps.Length; Interlocked.Increment(ref step)) + await Task.Delay(500); +}); + +var spinner = new Spinner(); +await spinner.RunAsync(workTask, (builder, out handler) => { + var current = Math.Min(Volatile.Read(ref step), steps.Length - 1); + handler = builder.Build(OutputPipe.Error, $"Current step: {ConsoleColor.Green}{steps[current]}"); +}); +``` + +Use this when the spinner header should reflect concurrently changing state without locking around the render path. + +### Stateful overwrite rendering + +```csharp +Console.Overwrite(percent, static current => { + ProgressBar.Render(OutputPipe.Error, current, ConsoleColor.Cyan, maxLineWidth: 40); + Console.NewLine(OutputPipe.Error); + Console.WriteInterpolated(OutputPipe.Error, $"Downloading assets... {ConsoleColor.Cyan}{current}"); +}, lines: 2, pipe: OutputPipe.Error); +``` + +Prefer this shape for live `status + progress` regions. It keeps the state explicit, avoids closure allocations, and makes the rendered height obvious. + +### Overwrite loop cleanup + +```csharp +Console.Overwrite(() => { + Console.WriteLineInterpolated(OutputPipe.Error, $"Running..."); + ProgressBar.Render(OutputPipe.Error, percent, ConsoleColor.Cyan); +}, lines: 2, pipe: OutputPipe.Error); + +Console.ClearNextLines(2, OutputPipe.Error); +``` + +### High-frequency concurrent status updates + +Use one reader task to own all `Console.Overwrite(...)` calls and let concurrent workers publish snapshots through a bounded channel: + +```csharp +using System.Threading.Channels; + +var channel = Channel.CreateBounded(new BoundedChannelOptions(1) { + SingleWriter = false, + SingleReader = true, + FullMode = BoundedChannelFullMode.DropWrite +}); + +_ = Task.Run(async () => { + await foreach (var stats in channel.Reader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { + Console.Overwrite(stats, static current => { + PrintMetrics(current); + }, lines: 2, pipe: OutputPipe.Error); + } + + Console.ClearNextLines(2, OutputPipe.Error); +}, cancellationToken); + +// Workers stay non-blocking and may skip intermediate frames when the UI is busy. +channel.Writer.TryWrite(latestStats); +``` + +Why this works: + +- only one reader ever renders, so `Overwrite` calls do not race each other +- capacity `1` + `DropWrite` avoids backpressure on workers during high-frequency updates +- this pattern is best when dropped intermediate states are acceptable and only recent snapshots matter + +## 6. Performance Checklist + +- Prefer interpolated handlers over string concatenation. +- Treat span/formattable `Write`/`WriteLine` overloads as advanced escape hatches, not default app-level APIs. +- Keep ANSI/decorations in interpolation holes, not raw literal spans. +- Use `OutputPipe.Error` for transient rendering. +- Avoid introducing wrapper abstractions when direct PrettyConsole APIs already solve the task. diff --git a/AGENTS.md b/AGENTS.md index d09488a..8c756ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Summary - PrettyConsole.Tests/ — interactive/demo runner (manually selects visual feature demos) - PrettyConsole.Tests.Unit/ — xUnit v3 unit tests using Microsoft Testing Platform - Examples/ — standalone `.cs` sample apps plus `assets/` previews; documented in `Examples/README.md` and excluded from automated builds/tests -- v5.3.1 (current) renames `IndeterminateProgressBar` to `Spinner` (and `AnimationSequence` to `Pattern`), triggers the line reset at the start of each spinner frame, gives all `RunAsync` overloads default cancellation tokens, and renames `ProgressBar.WriteProgressBar` to `Render` while adding handler-factory overloads and switching header parameters to `string`. It still passes handlers by `ref`, adds `AppendInline`, and introduces the ctor that takes only `OutputPipe` + optional `IFormatProvider`; `SkipLines` advances the cursor while keeping overwritten UIs; `Confirm(trueValues, ref handler, bool emptyIsTrue = true)` has the boolean last; spinner header factories use `PrettyConsoleInterpolatedStringHandlerFactory` with the singleton builder; `AnsiColors` is public. v5.2.0 rewrote the handler to buffer before writing and added `WhiteSpace`; v5.1.0 renamed `PrettyConsoleExtensions` to `ConsoleContext`, added `Console.WriteWhiteSpaces(length, pipe)`, and made `Out`/`Error`/`In` settable; v5.0.0 removed the legacy `ColoredOutput`/`Color` types in favor of `ConsoleColor` helpers and tuples. +- v5.4.0 (current) renames `IndeterminateProgressBar` to `Spinner` (and `AnimationSequence` to `Pattern`), triggers the line reset at the start of each spinner frame, gives all `RunAsync` overloads default cancellation tokens, and renames `ProgressBar.WriteProgressBar` to `Render` while adding handler-factory overloads and switching header parameters to `string`. It still passes handlers by `ref`, adds `AppendInline`, and introduces the ctor that takes only `OutputPipe` + optional `IFormatProvider`; `SkipLines` advances the cursor while keeping overwritten UIs; `Confirm(trueValues, ref handler, bool emptyIsTrue = true)` has the boolean last; spinner header factories use `PrettyConsoleInterpolatedStringHandlerFactory` with the singleton builder; `AnsiColors` is public. v5.2.0 rewrote the handler to buffer before writing and added `WhiteSpace`; v5.1.0 renamed `PrettyConsoleExtensions` to `ConsoleContext`, added `Console.WriteWhiteSpaces(length, pipe)`, and made `Out`/`Error`/`In` settable; v5.0.0 removed the legacy `ColoredOutput`/`Color` types in favor of `ConsoleColor` helpers and tuples. Commands you’ll use often @@ -56,9 +56,9 @@ High-level architecture and key concepts - Markup decorations - The `Markup` static class exposes ANSI sequences for underline, bold, italic, and strikethrough. Fields expand to escape codes only when output/error aren’t redirected; otherwise they collapse to empty strings so callers can safely interpolate them without extra checks. - Write APIs - - `WriteInterpolated`/`WriteLineInterpolated` host the interpolated-string handler; `Write`/`WriteLine` overloads target `ISpanFormattable` values (including `ref struct`s) and raw `ReadOnlySpan` spans with optional foreground/background overrides. Implementations rent buffers from `ArrayPool.Shared` to avoid allocation spikes and always reset colors. + - `WriteInterpolated`/`WriteLineInterpolated` are the default output APIs and host the interpolated-string handler; this path already covers high-performance formatting and coloring. Keep `Write`/`WriteLine` overloads (`ISpanFormattable`/`ReadOnlySpan`) for rare low-level scenarios where callers intentionally bypass the handler with custom formatting pipelines. Those overloads still rent buffers from `ArrayPool.Shared` and reset colors. - TextWriter helpers - - `ConsoleContext` surfaces the live `Out`/`Error` writers (now with public setters for test doubles) and keeps helpers like `GetWidthOrDefault`. Use `Console.WriteWhiteSpaces(int length, OutputPipe pipe = OutputPipe.Out)` for direct padding from call sites; `TextWriter.WriteWhiteSpaces(int)` remains available on the writers if you already have them on hand. + - `ConsoleContext` surfaces the live `Out`/`Error` writers (now with public setters for test doubles) and keeps helpers like `GetWidthOrDefault`. Use `Console.WriteWhiteSpaces(int length)` for the default output path and specify `OutputPipe.Error` only when needed; `TextWriter.WriteWhiteSpaces(int)` remains available on the writers if you already have them on hand. - Inputs - `ReadLine`/`TryReadLine` support `IParsable` types, optional defaults, enum parsing with `ignoreCase`, and interpolated prompts. `Confirm` exposes `DefaultConfirmValues`, overloads for custom truthy tokens, and interpolated prompts; `RequestAnyInput` blocks on `ReadKey` with colored prompts if desired. - Rendering controls @@ -83,7 +83,7 @@ Testing structure and workflows Notes and gotchas -- The library aims to minimize allocations; prefer span-based overloads (`ReadOnlySpan`, `ISpanFormattable`) plus the inline `ConsoleColor` tuples instead of recreating strings or structs. +- The library aims to minimize allocations; for normal app-level output prefer interpolated-handler APIs (`WriteInterpolated`/`WriteLineInterpolated`) plus inline `ConsoleColor` tuples. Use span-based `Write`/`WriteLine` overloads only for rare low-level formatting bypass scenarios. - When authoring new features, pick the appropriate OutputPipe to keep CLI piping behavior intact. - On macOS terminals, ANSI is supported; Windows legacy terminals are handled via ANSI-compatible rendering in the library. - `ProgressBar.Update` re-renders on every call (even when the percentage is unchanged) and accepts `sameLine` to place the status above the bar; the static `ProgressBar.Render` renders one-off bars without writing a trailing newline, so rely on `Console.Overwrite`/`lines` to stack multiple bars cleanly. diff --git a/PrettyConsole/PrettyConsole.csproj b/PrettyConsole/PrettyConsole.csproj index e5b135f..5bb74bd 100755 --- a/PrettyConsole/PrettyConsole.csproj +++ b/PrettyConsole/PrettyConsole.csproj @@ -13,7 +13,7 @@ true true snupkg - true + true true David Shnayder @@ -26,7 +26,7 @@ https://github.com/dusrdev/PrettyConsole/ https://github.com/dusrdev/PrettyConsole/ git - 5.4.0 + 5.4.1 enable MIT True @@ -34,7 +34,7 @@ - + @@ -55,6 +55,14 @@ + + + + + + PrettyConsoleExpert + .agents/skills/pretty-console-expert + diff --git a/PrettyConsole/PrettyConsoleInterpolatedStringHandler.cs b/PrettyConsole/PrettyConsoleInterpolatedStringHandler.cs index cd59cb4..6c32090 100644 --- a/PrettyConsole/PrettyConsoleInterpolatedStringHandler.cs +++ b/PrettyConsole/PrettyConsoleInterpolatedStringHandler.cs @@ -311,7 +311,6 @@ private void AppendSpanFormattable(T value, int alignment, string? format) ReadOnlySpan formatSpan = format.AsSpan(); int charsWritten; - // int start = _index; while (true) { Span dest = _buffer.AsSpan(_index); diff --git a/README.md b/README.md index 44a5909..ed1aff6 100755 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ PrettyConsole is a high-performance, ultra-low-latency, allocation-free extensio - 🔁 Advanced rendering primitives (`Overwrite`, `ClearNextLines`, `GoToLine`, `SkipLines`, progress bars) that respect console pipes - 🧱 Handler-aware `WhiteSpace` struct for zero-allocation padding directly inside interpolated strings - 🧰 Rich input helpers (`TryReadLine`, `Confirm`, `RequestAnyInput`) with `IParsable` and enum support -- ⚙️ Allocation-conscious span-first APIs (`ISpanFormattable`, `ReadOnlySpan`, `Console.WriteWhiteSpaces` / `TextWriter.WriteWhiteSpaces`) -- ⛓ Output routing through `OutputPipe.Out` and `OutputPipe.Error` so piping/redirects continue to work +- ⚙️ Low-level output escape hatches (`Console.Write/WriteLine` span and `ISpanFormattable` overloads) for advanced custom formatting pipelines +- ⛓ Output routing through `OutputPipe.Out` (default) and `OutputPipe.Error` so piping/redirects continue to work ## Performance @@ -35,6 +35,29 @@ PrettyConsole is **the go-to choice for ultra-low-latency, allocation-free conso dotnet add package PrettyConsole ``` +## Included Skill + +`PrettyConsole` ships a universal skill named `pretty-console-expert`. The package uses `Payload` to copy it into the consuming repository during build at `.agents/skills/pretty-console-expert`, giving the repo its own PrettyConsole expert. + +Consumers can control that payload with `PayloadPolicy`: + +```xml + + + +``` + +- `CopyOnBuild="true"` is the default and keeps the skill synchronized on build. Set it to `false` to stop future copies. +- `OverridePath` is optional and changes the destination base path. The package-defined `TargetPath` stays `.agents/skills/pretty-console-expert`, so `OverridePath="/custom/root"` copies the skill to `/custom/root/.agents/skills/pretty-console-expert`. + +For .NET 10 file-based apps, if repo-root detection is not available, set `PayloadRootDirectory` explicitly: + +```csharp +#:property PayloadRootDirectory=. +``` + ## Examples Standalone samples made with .NET 10 file-based apps with preview clips are available in [Examples](Examples/README.md). @@ -120,17 +143,26 @@ Avoid embedding the escape directly in the literal (`"\u001b[38;5;213maccent tex // Interpolated text Console.WriteInterpolated($"Processed {items} items in {elapsed:duration}"); // Processed 42 items in 3h 44m 9s Console.WriteLineInterpolated(OutputPipe.Error, $"{ConsoleColor.Magenta}debug{ConsoleColor.Default}"); +``` + +`WriteInterpolated` / `WriteLineInterpolated` should be your default output path. The handler already applies the high-performance formatting path internally and supports inline colors/alignment/specifiers. +### Low-level output escape hatch (rare) + +Use these only when you intentionally bypass the interpolated handler and own formatting end-to-end (advanced/manual pipelines): + +```csharp // Span + color overloads (no boxing) ReadOnlySpan header = "Title"; -Console.Write(header, OutputPipe.Out, ConsoleColor.White, ConsoleColor.DarkBlue); -Console.NewLine(); // writes newline to the default output pipe +Console.Write(header, OutputPipe.Error, ConsoleColor.White, ConsoleColor.DarkBlue); +Console.NewLine(OutputPipe.Error); // ISpanFormattable (works with ref structs) -Console.Write(percentage, OutputPipe.Out, ConsoleColor.Cyan, ConsoleColor.DefaultBackground, format: "F2", formatProvider: null); +Console.Write(percentage); // uses the default output pipe +Console.Write(percentage, OutputPipe.Error, ConsoleColor.Cyan, ConsoleColor.DefaultBackground, format: "F2", formatProvider: null); ``` -Behind the scenes these overloads rent buffers from the shared `ArrayPool` and route output to the correct pipe through `ConsoleContext.GetWriter`. +These overloads stay public mainly for compatibility and niche scenarios; for normal app code, prefer interpolated handler calls. ### Basic inputs @@ -158,7 +190,7 @@ if (!Console.Confirm($"Deploy to production? ({ConsoleColor.Green}y{ConsoleColor return; } -var customTruths = new[] { "sure", "do it" }; +string[] customTruths = ["sure", "do it"]; bool overwrite = Console.Confirm(customTruths, $"Overwrite existing files? ", emptyIsTrue: false); ``` @@ -203,19 +235,19 @@ Always call `Console.ClearNextLines(totalLines, pipe)` once after the last `Over ### Menus and tables ```csharp -var choice = Console.Selection("Pick an environment:", new[] { "Dev", "QA", "Prod" }); -var multi = Console.MultiSelection("Services to restart:", new[] { "API", "Worker", "Scheduler" }); +var choice = Console.Selection("Pick an environment:", ["Dev", "QA", "Prod"]); +var multi = Console.MultiSelection("Services to restart:", ["API", "Worker", "Scheduler"]); var (area, action) = Console.TreeMenu("Actions", new Dictionary> { - ["Users"] = new[] { "List", "Create", "Disable" }, - ["Jobs"] = new[] { "Queue", "Retry" } + ["Users"] = ["List", "Create", "Disable"], + ["Jobs"] = ["Queue", "Retry"] }); Console.Table( - headers: new[] { "Name", "Status" }, - columns: new[] { - new[] { "API", "Worker" }, - new[] { "Running", "Stopped" } - } + headers: ["Name", "Status"], + columns: [ + ["API", "Worker"], + ["Running", "Stopped"] + ] ); ``` @@ -224,7 +256,7 @@ Menus validate user input (throwing `ArgumentException` on invalid selections) a ### Progress bars ```csharp -using var progress = new ProgressBar { +var progress = new ProgressBar { ProgressChar = '■', ForegroundColor = ConsoleColor.DarkGray, ProgressColor = ConsoleColor.Green, @@ -262,7 +294,7 @@ The factory runs each frame so you can inject dynamic status text without alloca using System.Linq; using System.Threading.Channels; -var downloads = new[] { "Video.mp4", "Archive.zip", "Assets.pak" }; +string[] downloads = ["Video.mp4", "Archive.zip", "Assets.pak"]; var progress = new double[downloads.Length]; var updates = Channel.CreateUnbounded<(int index, double percent)>();