diff --git a/docs/adr/ADR0001V01R01-SplitConsolePlusAndPromptPlus.md b/docs/adr/ADR0001V01R01-SplitConsolePlusAndPromptPlus.md
index 556979e..6816a7c 100644
--- a/docs/adr/ADR0001V01R01-SplitConsolePlusAndPromptPlus.md
+++ b/docs/adr/ADR0001V01R01-SplitConsolePlusAndPromptPlus.md
@@ -17,7 +17,7 @@
# ADR0001V01R01
-[ADR Index](README.md) • **Next:** [ADR0002 →](ADR0002V01R01-ImmutableCapabilityProfile.md)
+[ADR Index](README.md) • **Next:** [ADR0002 →](ADR0002V01R02-ImmutableCapabilityProfile.md)
---
diff --git a/docs/adr/ADR0002V01R01-ImmutableCapabilityProfile.md b/docs/adr/ADR0002V01R01-ImmutableCapabilityProfile.md
deleted file mode 100644
index 5a69a63..0000000
--- a/docs/adr/ADR0002V01R01-ImmutableCapabilityProfile.md
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-

-
- # ADR0002V01R01
-
-
-[← ADR0001](ADR0001V01R01-SplitConsolePlusAndPromptPlus.md) • [ADR Index](README.md) • **Next:** [ADR0003 →](ADR0003V01R01-AutomaticInitializationOnFirstAccess.md)
-
----
-
-
-
-# ADR0002V01R01 — Immutable capability profile
-
-- **Status:** Accepted
-- **Version:** V01 / Revision R01
-- **Created:** 2026-07-24
-
-## Context
-
-Rendering decisions (ANSI, Unicode, color depth, interactivity) must be
-consistent for the whole process lifetime. Re-detecting per call would be slow
-and could produce inconsistent output if the environment appears to change.
-
-## Decision
-
-Detect the environment **once** and expose it as an **immutable snapshot**,
-`ConsolePlus.Profile` typed as `IProfileReadOnly`. The profile describes
-`ProfileName`, `IsTerminal`, `Interactive`, `SupportUnicode`, `SupportsAnsi`,
-`ColorDepth`, captured culture, default/original colors and encodings. Nothing
-in the public API mutates the profile after initialization.
-
-## Consequences
-
-- **Positive:** stable, self-consistent rendering; detection cost paid once;
- read-only contract prevents accidental capability changes mid-run.
-- **Negative / trade-off:** environment changes after startup (e.g. a resize of
- color support) are not reflected; acceptable because such changes are rare and
- restart-scoped.
diff --git a/docs/adr/ADR0002V01R02-ImmutableCapabilityProfile.md b/docs/adr/ADR0002V01R02-ImmutableCapabilityProfile.md
new file mode 100644
index 0000000..e5f7153
--- /dev/null
+++ b/docs/adr/ADR0002V01R02-ImmutableCapabilityProfile.md
@@ -0,0 +1,75 @@
+
+
+
+
+

+
+ # ADR0002V01R02
+
+
+[← ADR0001](ADR0001V01R01-SplitConsolePlusAndPromptPlus.md) • [ADR Index](README.md) • **Next:** [ADR0003 →](ADR0003V01R01-AutomaticInitializationOnFirstAccess.md)
+
+---
+
+# ADR0002V01R02 — Immutable capability profile
+
+- **Status:** Accepted
+- **Version:** V01 / Revision R02
+- **Created:** 2026-07-24
+- **Changed:** 2026-07-25 (R02 — documented the real override path; removed a dead API that
+ contradicted this decision)
+
+## Context
+
+Rendering decisions (ANSI, Unicode, color depth, interactivity) must be
+consistent for the whole process lifetime. Re-detecting per call would be slow
+and could produce inconsistent output if the environment appears to change.
+
+## Decision
+
+Detect the environment **once** and expose it as an **immutable snapshot**,
+`ConsolePlus.Profile` typed as `IProfileReadOnly`. The profile describes
+`ProfileName`, `IsTerminal`, `Interactive`, `SupportUnicode`, `SupportsAnsi`,
+`ColorDepth`, captured culture, default/original colors and encodings. Nothing
+in the public API mutates the profile after initialization.
+
+**R02 addendum — the one supported override path, and a correction:** callers who need to
+override auto-detected values do so via an optional `ConsoleProfile.json` file next to the
+executable, read once in `EnvironmentUtil.CreateProfile` **before** detection and before any
+downstream caching (e.g. `ConsoleWriter` caches `Profile.ColorDepth` in a `readonly` field at
+construction; the ANSI-vs-NoAnsi adapter choice is also made once, at startup — see
+[ADR0004V01R01](ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md)). This is why a JSON-file override
+works fully while a hypothetical post-init C# mutation API could not — by the time user code could
+call it, the values it would change are already cached elsewhere. See
+`docs/environment-detection.md` → "Override detection" for the full JSON schema.
+
+This addendum exists because a 2026-07-25 docs audit found a public interface, `IProfileSetup`
+(a fluent builder with `SupportsAnsi(AutoDetect)`, `ColorDepth(ColorSystem)`, `Apply()`, etc.), that
+had **zero implementers and zero callers anywhere in the repository** — `ProfileConsole` (the
+concrete class backing `IProfileReadOnly`) never implemented it, and one of its members
+(`TimeMsResizeChange`) was referenced nowhere else at all. It directly contradicted this ADR's
+"nothing... mutates the profile after initialization" line. It has been **deleted outright** — the
+JSON mechanism above already covered everything it promised, and does so correctly (unaffected by
+the caching problem a post-init API would hit). No consumer depended on it (ConsolePlus is
+pre-1.0/Beta, so this is not considered a breaking change).
+
+## Consequences
+
+- **Positive:** stable, self-consistent rendering; detection cost paid once;
+ read-only contract prevents accidental capability changes mid-run; the one real override path
+ (`ConsoleProfile.json`) is now documented where users would look for it, and no orphaned API
+ contradicts the immutability guarantee.
+- **Negative / trade-off:** environment changes after startup (e.g. a resize of
+ color support) are not reflected; acceptable because such changes are rare and
+ restart-scoped. Overriding via `ConsoleProfile.json` requires a restart to take effect (the file
+ is only read at startup) — there is no live-reload.
diff --git a/docs/adr/ADR0003V01R01-AutomaticInitializationOnFirstAccess.md b/docs/adr/ADR0003V01R01-AutomaticInitializationOnFirstAccess.md
index a2acbe3..36d0811 100644
--- a/docs/adr/ADR0003V01R01-AutomaticInitializationOnFirstAccess.md
+++ b/docs/adr/ADR0003V01R01-AutomaticInitializationOnFirstAccess.md
@@ -17,7 +17,7 @@
# ADR0003V01R01
-[← ADR0002](ADR0002V01R01-ImmutableCapabilityProfile.md) • [ADR Index](README.md) • **Next:** [ADR0004 →](ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md)
+[← ADR0002](ADR0002V01R02-ImmutableCapabilityProfile.md) • [ADR Index](README.md) • **Next:** [ADR0004 →](ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md)
---
diff --git a/docs/adr/ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md b/docs/adr/ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md
index b27f223..8b59fab 100644
--- a/docs/adr/ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md
+++ b/docs/adr/ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md
@@ -17,7 +17,7 @@
# ADR0004V01R01
-[← ADR0003](ADR0003V01R01-AutomaticInitializationOnFirstAccess.md) • [ADR Index](README.md) • **Next:** [ADR0005 →](ADR0005V01R01-AnsiconLegacyWindowsSupport.md)
+[← ADR0003](ADR0003V01R01-AutomaticInitializationOnFirstAccess.md) • [ADR Index](README.md) • **Next:** [ADR0005 →](ADR0005V01R02-AnsiconLegacyWindowsSupport.md)
---
diff --git a/docs/adr/ADR0005V01R01-AnsiconLegacyWindowsSupport.md b/docs/adr/ADR0005V01R01-AnsiconLegacyWindowsSupport.md
deleted file mode 100644
index 40ba4ac..0000000
--- a/docs/adr/ADR0005V01R01-AnsiconLegacyWindowsSupport.md
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-

-
- # ADR0005V01R01
-
-
-[← ADR0004](ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md) • [ADR Index](README.md) • **Next:** [ADR0006 →](ADR0006V01R01-AutoDetectTriState.md)
-
----
-
-
-
-# ADR0005V01R01 — ANSICON injection for legacy Windows
-
-- **Status:** Accepted
-- **Version:** V01 / Revision R01
-- **Created:** 2026-07-24
-
-## Context
-
-Modern Windows (10+) supports ANSI via Virtual Terminal Processing, but
-pre-Windows 10 consoles do not. Requiring users to install a third-party tool
-would break the "just works" promise on legacy systems.
-
-## Decision
-
-Bundle [ANSICON](https://github.com/adoxa/ansicon) and **inject it
-automatically** on legacy Windows consoles that lack native ANSI support. The
-injection uses the `LdrLoadDll` approach via `CreateRemoteThread` for 64-bit
-.NET AnyCPU processes, providing transparent ANSI escape-sequence support with
-no manual installation or configuration.
-
-## Consequences
-
-- **Positive:** ANSI rendering works transparently on old Windows without user
- action.
-- **Negative / trade-off:** ships a native dependency and performs process
- injection, which some environments/AV tooling may flag; scoped strictly to
- legacy Windows where native ANSI is absent.
diff --git a/docs/adr/ADR0005V01R02-AnsiconLegacyWindowsSupport.md b/docs/adr/ADR0005V01R02-AnsiconLegacyWindowsSupport.md
new file mode 100644
index 0000000..c692a0c
--- /dev/null
+++ b/docs/adr/ADR0005V01R02-AnsiconLegacyWindowsSupport.md
@@ -0,0 +1,61 @@
+
+
+
+
+

+
+ # ADR0005V01R02
+
+
+[← ADR0004](ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md) • [ADR Index](README.md) • **Next:** [ADR0006 →](ADR0006V01R01-AutoDetectTriState.md)
+
+---
+
+# ADR0005V01R02 — ANSICON launch (not injection) for legacy Windows
+
+- **Status:** Accepted
+- **Version:** V01 / Revision R02
+- **Created:** 2026-07-24
+- **Changed:** 2026-07-25 (R02 — corrected the mechanism description; the original text described
+ DLL injection, but the real implementation never did that)
+
+## Context
+
+Modern Windows (10+) supports ANSI via Virtual Terminal Processing, but
+pre-Windows 10 consoles do not. Requiring users to install a third-party tool
+would break the "just works" promise on legacy systems.
+
+**R02 correction:** the original (R01) text of this ADR described the mechanism as DLL injection
+(`LdrLoadDll` via `CreateRemoteThread`). A 2026-07-25 docs audit traced the real implementation in
+`src/Core/LegacyAnsiBootstrapper.cs` and found no DLL injection or remote-thread APIs anywhere in
+this codebase — the R01 description was inaccurate from the start (the same inaccurate claim had
+also propagated into 3 hand-written docs, corrected in the same pass).
+
+## Decision
+
+Bundle [ANSICON](https://github.com/adoxa/ansicon) and **launch it
+automatically** on legacy Windows consoles that lack native ANSI support.
+`LegacyAnsiBootstrapper` runs the bundled `ansicon.exe` (matching the process
+architecture, x86 or x64) via `Process.Start(..., "-p")` and waits for it to
+exit — no DLL injection or remote-thread APIs are involved — providing
+transparent ANSI escape-sequence support with no manual installation or
+configuration.
+
+## Consequences
+
+- **Positive:** ANSI rendering works transparently on old Windows without user
+ action.
+- **Negative / trade-off:** ships a native third-party executable per
+ architecture, which some environments/AV tooling may flag merely for being an
+ unfamiliar bundled binary; scoped strictly to legacy Windows where native
+ ANSI is absent.
diff --git a/docs/adr/ADR0006V01R01-AutoDetectTriState.md b/docs/adr/ADR0006V01R01-AutoDetectTriState.md
index 8b08d9d..d3f6b5b 100644
--- a/docs/adr/ADR0006V01R01-AutoDetectTriState.md
+++ b/docs/adr/ADR0006V01R01-AutoDetectTriState.md
@@ -17,7 +17,7 @@
# ADR0006V01R01
-[← ADR0005](ADR0005V01R01-AnsiconLegacyWindowsSupport.md) • [ADR Index](README.md) • **Next:** [ADR0007 →](ADR0007V01R01-GracefulDegradation.md)
+[← ADR0005](ADR0005V01R02-AnsiconLegacyWindowsSupport.md) • [ADR Index](README.md) • **Next:** [ADR0007 →](ADR0007V01R01-GracefulDegradation.md)
---
diff --git a/docs/adr/ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md b/docs/adr/ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md
index 6a2747e..0aae93a 100644
--- a/docs/adr/ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md
+++ b/docs/adr/ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md
@@ -17,7 +17,7 @@
# ADR0014V01R01
-[← ADR0013](ADR0013V01R01-EmojiShortcodeModel.md) • [ADR Index](README.md) • **Next:** [ADR0015 →](ADR0015V01R01-GeneratedApiDocsOffLimits.md)
+[← ADR0013](ADR0013V01R01-EmojiShortcodeModel.md) • [ADR Index](README.md) • **Next:** [ADR0015 →](ADR0015V01R02-GeneratedApiDocsOffLimits.md)
---
diff --git a/docs/adr/ADR0015V01R01-GeneratedApiDocsOffLimits.md b/docs/adr/ADR0015V01R01-GeneratedApiDocsOffLimits.md
deleted file mode 100644
index a42cbd7..0000000
--- a/docs/adr/ADR0015V01R01-GeneratedApiDocsOffLimits.md
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-

-
- # ADR0015V01R01
-
-
-[← ADR0014](ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md) • [ADR Index](README.md)
-
----
-
-
-
-# ADR0015V01R01 — Generated API docs are off-limits for manual edits
-
-- **Status:** Accepted
-- **Version:** V01 / Revision R01
-- **Created:** 2026-07-24
-
-## Context
-
-The `docs/api/` folder is generated from XML doc comments by the documentation
-tooling. Manual edits there are silently overwritten on the next build and give
-a false impression of being authoritative.
-
-## Decision
-
-`docs/api/` is **generated output** and must never be edited by hand. All API
-documentation changes are made in the source XML doc comments. Narrative and
-conceptual documentation lives in the hand-written `docs/*.md` files, which are
-the only Markdown docs that may be edited directly. See
-`docs/api-documentation-guide.md`.
-
-## Consequences
-
-- **Positive:** single source of truth for API docs (the code); no lost edits.
-- **Negative / trade-off:** correcting API text requires a code change and a
- regeneration step rather than a quick Markdown edit.
diff --git a/docs/adr/ADR0015V01R02-GeneratedApiDocsOffLimits.md b/docs/adr/ADR0015V01R02-GeneratedApiDocsOffLimits.md
new file mode 100644
index 0000000..efdda6e
--- /dev/null
+++ b/docs/adr/ADR0015V01R02-GeneratedApiDocsOffLimits.md
@@ -0,0 +1,74 @@
+
+
+
+
+

+
+ # ADR0015V01R02
+
+
+[← ADR0014](ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md) • [ADR Index](README.md)
+
+---
+
+# ADR0015V01R02 — Generated API docs are off-limits for manual edits; regeneration is gated on `ReleaseDoc`, not `Release`
+
+- **Status:** Accepted
+- **Version:** V01 / Revision R02
+- **Created:** 2026-07-24
+- **Changed:** 2026-07-25 (R02 — fixed the build-configuration gate that actually triggers
+ regeneration)
+
+## Context
+
+The `docs/api/` folder is generated from XML doc comments by the documentation
+tooling. Manual edits there are silently overwritten on the next build and give
+a false impression of being authoritative.
+
+`src/ConsolePlus.csproj` declares `Debug;Release;ReleaseDoc` — a
+dedicated `ReleaseDoc` configuration exists precisely so that regenerating `docs/api/` (via
+`DefaultDocumentation`) is a deliberate, separate step from `Release`, which is used to pack and
+publish the NuGet package. This mirrors the sibling PromptPlus repository's already-correct setup.
+
+**R02 finding:** while regenerating `docs/api/` for an unrelated change, `dotnet build -c
+ReleaseDoc` produced no `DefaultDocumentation` output at all. The `PackageReference`/
+`PropertyGroup`/`AddDocIconHeader` target conditions in the csproj all still checked
+`'$(Configuration)' == 'Release'` — a leftover from before the `ReleaseDoc` configuration was
+introduced into the `Configurations` list. `ReleaseDoc` existed as a buildable configuration name
+but nothing was actually gated on it; `Release` builds were silently paying the
+`DefaultDocumentation` cost that the dedicated configuration was supposed to isolate.
+
+## Decision
+
+`docs/api/` is **generated output** and must never be edited by hand. All API
+documentation changes are made in the source XML doc comments. Narrative and
+conceptual documentation lives in the hand-written `docs/*.md` files, which are
+the only Markdown docs that may be edited directly. See
+`docs/api-documentation-guide.md`.
+
+**R02 addendum:** changed all three conditions (`ItemGroup` `PackageReference`, `PropertyGroup`
+with the `DefaultDocumentation*` properties, and the `AddDocIconHeader` target) from
+`'$(Configuration)' == 'Release'` to `'$(Configuration)' == 'ReleaseDoc'`. `docs/api/` regeneration
+now requires `dotnet build src/ConsolePlus.csproj -c ReleaseDoc -f net10.0`; `-c Release` no longer
+touches `docs/api/` at all. `docs/api-documentation-guide.md` updated to match.
+
+## Consequences
+
+- **Positive:** single source of truth for API docs (the code); no lost edits. `Release` builds
+ (used for NuGet packing) no longer carry the `DefaultDocumentation` package/analysis cost;
+ regeneration is now an explicit, intentional step, matching the sibling PromptPlus repo.
+- **Negative / trade-off:** correcting API text requires a code change and a
+ regeneration step rather than a quick Markdown edit. Anyone with a local habit of running
+ `-c Release` to pick up doc changes must switch to `-c ReleaseDoc`. No CI workflow depended on
+ the old behavior (verified: neither `ci.yml` nor `publish-nuget.yml` reference `docs/api` or
+ `DefaultDocumentation`), so this has no automation impact.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index f5f2e0d..9513187 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -46,10 +46,10 @@ projects (ConsolePlus + PromptPlus).
| ADR | Title | Version | Status |
| --- | --- | --- | --- |
| [ADR0001V01R01](ADR0001V01R01-SplitConsolePlusAndPromptPlus.md) | Split PromptPlus 5.x into two projects (ConsolePlus + PromptPlus) | V01 | Accepted |
-| [ADR0002V01R01](ADR0002V01R01-ImmutableCapabilityProfile.md) | Immutable capability profile | V01 | Accepted |
+| [ADR0002V01R02](ADR0002V01R02-ImmutableCapabilityProfile.md) | Immutable capability profile | V01 (R02) | Accepted |
| [ADR0003V01R01](ADR0003V01R01-AutomaticInitializationOnFirstAccess.md) | Automatic initialization on first access | V01 | Accepted |
| [ADR0004V01R01](ADR0004V01R01-AnsiVsNonAnsiDriverSelection.md) | ANSI vs non-ANSI driver selection | V01 | Accepted |
-| [ADR0005V01R01](ADR0005V01R01-AnsiconLegacyWindowsSupport.md) | ANSICON injection for legacy Windows | V01 | Accepted |
+| [ADR0005V01R02](ADR0005V01R02-AnsiconLegacyWindowsSupport.md) | ANSICON launch (not injection) for legacy Windows | V01 (R02) | Accepted |
| [ADR0006V01R01](ADR0006V01R01-AutoDetectTriState.md) | `AutoDetect` tri-state for capabilities | V01 | Accepted |
| [ADR0007V01R01](ADR0007V01R01-GracefulDegradation.md) | Profile-driven graceful degradation | V01 | Accepted |
| [ADR0008V01R01](ADR0008V01R01-ColorDepthModel.md) | Color depth model (`ColorSystem`) | V01 | Accepted |
@@ -59,4 +59,4 @@ projects (ConsolePlus + PromptPlus).
| [ADR0012V01R01](ADR0012V01R01-ShutdownStateRestoration.md) | Shutdown state restoration | V01 | Accepted |
| [ADR0013V01R01](ADR0013V01R01-EmojiShortcodeModel.md) | Emoji shortcode model with fallback | V01 | Accepted |
| [ADR0014V01R01](ADR0014V01R01-LowLevelAnsiAndAlternateScreen.md) | Low-level ANSI and alternate-screen API | V01 | Accepted |
-| [ADR0015V01R01](ADR0015V01R01-GeneratedApiDocsOffLimits.md) | Generated API docs are off-limits for manual edits | V01 | Accepted |
+| [ADR0015V01R02](ADR0015V01R02-GeneratedApiDocsOffLimits.md) | Generated API docs are off-limits for manual edits; regeneration gated on `ReleaseDoc` | V01 (R02) | Accepted |
diff --git a/docs/advanced-api.md b/docs/advanced-api.md
index d744777..0c2409a 100644
--- a/docs/advanced-api.md
+++ b/docs/advanced-api.md
@@ -132,21 +132,24 @@ Emacs-style editing semantics (cursor movement, word operations, kill/yank, case
in-memory string. Expose it directly when you are building a **custom input loop** and want the same
editing behavior ConsolePlus uses.
-Create one with three options:
+Create one with four options:
- `isreadlonly` — when `true`, the buffer rejects edits (useful for read-only display).
- `caseOption` — a [`CaseOptions`](reading-input.md#case-transformation-and-length-limits) value
(`Any`, `Uppercase`, or `Lowercase`) applied to typed characters.
+- `enableEmacsKeys` — when `true`, Emacs-style key bindings (Ctrl+A/E/B/F/K/…, Alt+B/F/…) are
+ active; when `false`, they're no-ops and only plain character entry/navigation works.
- `validate` — an optional `Func` that filters which characters are accepted.
```csharp
using ConsolePlusLibrary;
using System;
-// A digit-only, upper-case buffer
+// A digit-only, upper-case buffer with Emacs key bindings enabled
var buffer = new EmacsConsoleBuffer(
isreadlonly: false,
caseOption: CaseOptions.Uppercase,
+ enableEmacsKeys: true,
validate: char.IsLetterOrDigit);
// Feed keys from your own read loop
@@ -189,6 +192,8 @@ does — for example when aligning columns that may contain wide (CJK) character
| `NormalizeNewLines()` | `string` | Converts all line endings to `Environment.NewLine` |
| `SplitLines()` | `string[]` | Splits text into lines after normalizing line endings |
| `GetDisplayLength()` | `int[]` | Display width of each line, counting wide characters as 2 columns |
+| `TruncateToDisplayWidth(int maxWidth)` | `string` | Truncates text to fit within `maxWidth` display columns (not character count) |
+| `GetRuneWidth()` (on `Rune`) | `int` | Display width, in columns, of a single `Rune` |
```csharp
using ConsolePlusLibrary;
@@ -280,7 +285,7 @@ ConsolePlus.RunAtomic(() => ConsolePlus.WriteLine("one uninterrupted unit"));
Fragment[] parts = Fragment.FromText("[Red]hi[/]", ConsolePlus.CurrentStyle);
// Custom Emacs input buffer
-var buffer = new EmacsConsoleBuffer(false, CaseOptions.Any, char.IsLetterOrDigit);
+var buffer = new EmacsConsoleBuffer(false, CaseOptions.Any, true, char.IsLetterOrDigit);
// String measuring/normalizing (wide-char aware)
int[] widths = "Hello\n世界".GetDisplayLength(); // [5, 4]
diff --git a/docs/api-documentation-guide.md b/docs/api-documentation-guide.md
index 821b0e7..dc15880 100644
--- a/docs/api-documentation-guide.md
+++ b/docs/api-documentation-guide.md
@@ -31,17 +31,18 @@ A configuração do DefaultDocumentation está no arquivo `src/ConsolePlus.cspro
True
-
-
+
+
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
+
..\docs\api
- Assembly, Namespaces , Types , Members
+ Assembly, Namespaces, Classes, Interfaces, Events, Enums, Structs, Delegates
+ NameAndMd5Mix
Public
ConsolePlus
https://raw.githubusercontent.com/FRACerqueira/ConsolePlus/main/icon.png
@@ -50,40 +51,44 @@ A configuração do DefaultDocumentation está no arquivo `src/ConsolePlus.cspro
```
> ℹ️ Após a geração, uma task MSBuild (`PrependDocIconHeader`) adiciona o cabeçalho com o ícone
-> (`DocIconUrl` / `DocIconWidth`) no topo de cada arquivo `.md` gerado em `docs/api`.
+> (`DocIconUrl` / `DocIconWidth`) no topo de cada arquivo `.md` gerado em `docs/api`. Note que o
+> `PrivateAssets`/`IncludeAssets` do `PackageReference` está comentado no projeto real — deixado como
+> referência caso seja necessário reativar, não como configuração ativa.
### Opções de Configuração
| Propriedade | Valor | Descrição |
|-------------|-------|-----------|
-| `Condition` | `Release` + `net10.0` | **Documentação gerada APENAS em builds Release do target net10.0** |
+| `Condition` | `ReleaseDoc` + `net10.0` | **Documentação gerada APENAS em builds da configuração `ReleaseDoc` no target net10.0** (não em `Release`, que é usado para empacotar o NuGet sem regenerar docs) |
| `DefaultDocumentationFolder` | `../docs/api` | Pasta de saída para os arquivos Markdown |
-| `DefaultDocumentationGeneratedPages` | `Assembly, Namespaces, Types, Members` | Páginas geradas |
+| `DefaultDocumentationGeneratedPages` | `Assembly, Namespaces, Classes, Interfaces, Events, Enums, Structs, Delegates` | Páginas geradas |
+| `DefaultDocumentationFileNameFactory` | `NameAndMd5Mix` | Estratégia de nomeação dos arquivos gerados |
| `DefaultDocumentationGeneratedAccessModifiers` | `Public` | Documenta apenas membros públicos |
| `DefaultDocumentationAssemblyPageName` | `ConsolePlus` | Nome da página principal do assembly (`ConsolePlus.md`) |
| `DocIconUrl` / `DocIconWidth` | icon.png / `120` | Cabeçalho com ícone adicionado a cada `.md` gerado |
## 🔄 Regenerando a Documentação
-A documentação é regenerada automaticamente toda vez que você compila o projeto em **Release**
-para o target **net10.0**:
+A documentação é regenerada automaticamente toda vez que você compila o projeto na configuração
+**ReleaseDoc** para o target **net10.0**:
### Via Visual Studio
1. Abra a solução no Visual Studio
-2. Mude para configuração **Release**
+2. Mude para configuração **ReleaseDoc**
3. Build → Build Solution (Ctrl+Shift+B)
4. Os arquivos Markdown serão atualizados em `docs/api/`
-**Nota**: Em builds **Debug** (ou em targets diferentes de net10.0), a documentação **não** é
-gerada, para acelerar o desenvolvimento.
+**Nota**: Em builds **Debug** ou **Release** (ou em targets diferentes de net10.0), a documentação
+**não** é gerada — `Release` é usado apenas para empacotar o NuGet, sem o custo do DefaultDocumentation.
### Via Linha de Comando
```bash
-# Na raiz do repositório - APENAS Release gera documentação (target net10.0)
-dotnet build src/ConsolePlus.csproj -c Release -f net10.0
+# Na raiz do repositório - APENAS ReleaseDoc gera documentação (target net10.0)
+dotnet build src/ConsolePlus.csproj -c ReleaseDoc -f net10.0
-# Build Debug NÃO gera documentação
+# Build Debug ou Release NÃO gera documentação
dotnet build src/ConsolePlus.csproj -c Debug
+dotnet build src/ConsolePlus.csproj -c Release
```
### Verificando os arquivos gerados
diff --git a/docs/api/ConsolePlusLibrary.md b/docs/api/ConsolePlusLibrary.md
index e8bfa98..49e092c 100644
--- a/docs/api/ConsolePlusLibrary.md
+++ b/docs/api/ConsolePlusLibrary.md
@@ -40,7 +40,6 @@
| [IBanner](IBanner.md 'ConsolePlusLibrary\.IBanner') | Represents a banner that can be customized and displayed\. |
| [IConsole](IConsole.md 'ConsolePlusLibrary\.IConsole') | Interface for abstracting console operations\. |
| [IProfileReadOnly](IProfileReadOnly.md 'ConsolePlusLibrary\.IProfileReadOnly') | Defines a console profile describing capabilities, dimensions, colors and display behavior for the current console/terminal session\. |
-| [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') | Interface for configuring console profiles, allowing for the setup of various console settings such as encoding, color, and interaction capabilities\. |
| [IStringDash](IStringDash.md 'ConsolePlusLibrary\.IStringDash') | Represents a banner that can be customized and displayed\. |
| [IWidgets](IWidgets.md 'ConsolePlusLibrary\.IWidgets') | Represents a collection of widgets that can be used in the console application\. |
diff --git a/docs/api/IProfileSetup.md b/docs/api/IProfileSetup.md
deleted file mode 100644
index 54038bd..0000000
--- a/docs/api/IProfileSetup.md
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-#### [ConsolePlus\.net](ConsolePlus.md 'ConsolePlus')
-### [ConsolePlusLibrary](ConsolePlusLibrary.md 'ConsolePlusLibrary')
-
-## IProfileSetup Interface
-
-Interface for configuring console profiles, allowing for the setup of various console settings such as encoding, color, and interaction capabilities\.
-
-```csharp
-public interface IProfileSetup
-```
-### Methods
-
-
-
-## IProfileSetup\.Apply\(\) Method
-
-Applies the configured profile settings to the console environment\.
-This method should be called after all desired settings have been configured using the fluent interface methods\.
-
-```csharp
-void Apply();
-```
-
-
-
-## IProfileSetup\.ColorDepth\(ColorSystem\) Method
-
-Color depth \(capability\) of the console\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup ColorDepth(ConsolePlusLibrary.ColorSystem value);
-```
-#### Parameters
-
-
-
-`value` [ColorSystem](ColorSystem.md 'ConsolePlusLibrary\.ColorSystem')
-
-The color depth to set for the console\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.DefaultBackgroundColor\(Color\) Method
-
-Default background [Color](Color.md 'ConsolePlusLibrary\.Color') used when no explicit color is specified\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup DefaultBackgroundColor(ConsolePlusLibrary.Color value);
-```
-#### Parameters
-
-
-
-`value` [Color](Color.md 'ConsolePlusLibrary\.Color')
-
-The color to set as the default background color\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.DefaultInputEncoding\(Encoding\) Method
-
-Default input encoding of the console at the time the profile was created\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup DefaultInputEncoding(System.Text.Encoding value);
-```
-#### Parameters
-
-
-
-`value` [System\.Text\.Encoding](https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding 'System\.Text\.Encoding')
-
-The encoding to set as the default input encoding\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.DefaultOutputEncoding\(Encoding\) Method
-
-Default output encoding of the console at the time the profile was created\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup DefaultOutputEncoding(System.Text.Encoding value);
-```
-#### Parameters
-
-
-
-`value` [System\.Text\.Encoding](https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding 'System\.Text\.Encoding')
-
-The encoding to set as the default output encoding\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.Interactive\(bool\) Method
-
-Indicating whether or not the console supports interaction\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup Interactive(bool value);
-```
-#### Parameters
-
-
-
-`value` [System\.Boolean](https://learn.microsoft.com/en-us/dotnet/api/system.boolean 'System\.Boolean')
-
-A boolean value indicating whether the console supports interaction\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.SupportsAnsi\(AutoDetect\) Method
-
-Gets a value indicating whether ANSI escape sequences are supported for styling/output\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup SupportsAnsi(ConsolePlusLibrary.AutoDetect value);
-```
-#### Parameters
-
-
-
-`value` [AutoDetect](AutoDetect.md 'ConsolePlusLibrary\.AutoDetect')
-
-An [AutoDetect](AutoDetect.md 'ConsolePlusLibrary\.AutoDetect') value indicating whether ANSI escape sequences are supported\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.SupportUnicode\(AutoDetect\) Method
-
-Indicates whether Unicode output is fully supported\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup SupportUnicode(ConsolePlusLibrary.AutoDetect value);
-```
-#### Parameters
-
-
-
-`value` [AutoDetect](AutoDetect.md 'ConsolePlusLibrary\.AutoDetect')
-
-An [AutoDetect](AutoDetect.md 'ConsolePlusLibrary\.AutoDetect') value indicating whether Unicode output is supported\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
-
-
-
-## IProfileSetup\.TimeMsResizeChange\(int\) Method
-
-Time taken for the console to trigger a resize event after the console window has been resized\.
-This can be used to optimize performance by reducing the frequency of resize events, especially in scenarios where rapid resizing may occur\.
-Default value is 300 milliseconds, Range valid 100\-1000 milliseconds\.
-
-```csharp
-ConsolePlusLibrary.IProfileSetup TimeMsResizeChange(int value);
-```
-#### Parameters
-
-
-
-`value` [System\.Int32](https://learn.microsoft.com/en-us/dotnet/api/system.int32 'System\.Int32')
-
-The time in milliseconds to set for the resize event delay\.
-
-#### Returns
-[IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup')
-The current instance of [IProfileSetup](IProfileSetup.md 'ConsolePlusLibrary\.IProfileSetup') to allow for fluent configuration\.
\ No newline at end of file
diff --git a/docs/colors.md b/docs/colors.md
index 29e3b21..5fa70b0 100644
--- a/docs/colors.md
+++ b/docs/colors.md
@@ -59,7 +59,7 @@ using ConsolePlusLibrary;
var c1 = new Color(30, 144, 255);
// From a named CSS color (static properties)
-var c2 = Color.DodgerBlue;
+var c2 = Color.Dodgerblue;
// From HEX (throws on invalid input)
var c3 = Color.FromHex("#1E90FF");
@@ -101,8 +101,8 @@ All 148 CSS colors are available as static `Color` properties (case-insensitive
| `Color.Blue` |  | `#0000FF` |
| `Color.Teal` |  | `#008080` |
| `Color.Gold` |  | `#FFD700` |
-| `Color.HotPink` |  | `#FF69B4` |
-| `Color.RebeccaPurple` |  | `#663399` |
+| `Color.Hotpink` |  | `#FF69B4` |
+| `Color.Rebeccapurple` |  | `#663399` |
👉 Jump to the [Full color reference](#full-color-reference-visual) for all 148 with swatches.
@@ -194,7 +194,7 @@ double ratio = Color.White.GetContrast(Color.Navy);
Color fg = Color.GetContrastForegroundColor(Color.Gold); // -> black
// A quick "readable inverse" (black or white) based on brightness
-Color inv = Color.SteelBlue.GetInvertedColor();
+Color inv = Color.Steelblue.GetInvertedColor();
// Nudge a foreground color only if it fails a minimum contrast ratio
Color safe = Color.Yellow.AdjustForegroundColorForContrast(
diff --git a/docs/emoji.md b/docs/emoji.md
index b11a4b6..7bace3b 100644
--- a/docs/emoji.md
+++ b/docs/emoji.md
@@ -10,9 +10,9 @@
ConsolePlus ships with the full Unicode emoji catalog — **1,426** emoji across the **10 official
Unicode groups**, plus a few legacy aliases. You can drop them into any output using `:shortcode:`
-tokens (just like [markup](markup.md#emoji)), an optional `:group/shortcode:` form, reference them
-as strongly-typed constants on the static `Emoji` class, or use the group-typed
-`Emoji.Group.Name` form.
+tokens (just like [markup](markup.md#emoji)), an optional `:group/shortcode:` form, or reference
+them as strongly-typed constants — one public static class per Unicode group (`EmojiActivities`,
+`EmojiSymbols`, `EmojiTravelAndPlaces`, …).
## Table of contents
- [Compatibility considerations](#compatibility-considerations)
@@ -95,7 +95,7 @@ When using emoji in your application:
where your app is deployed
2. **Provide text fallbacks** — consider showing both emoji and text for critical information:
```csharp
- ConsolePlus.WriteLine($"{Emoji.Warning} Warning: Low disk space");
+ ConsolePlus.WriteLine($"{EmojiSymbols.Warning} Warning: Low disk space");
// Fallback: "⚠️ Warning: Low disk space" or just "Warning: Low disk space"
```
3. **Use semantic color markup** — combine emoji with [markup colors](markup.md) for emphasis even
@@ -118,15 +118,15 @@ ConsolePlus.WriteLine(":rocket: Launching...");
ConsolePlus.WriteLine("[Green]:check_mark_button: Success[/]");
// 2) Strongly-typed constants — great for building strings in code
-ConsolePlus.WriteLine($"{Emoji.Rocket} Launching...");
-ConsolePlus.WriteLine($"{Emoji.Fire} {Emoji.ThumbsUp} {Emoji.RedHeart}");
+ConsolePlus.WriteLine($"{EmojiTravelAndPlaces.Rocket} Launching...");
+ConsolePlus.WriteLine($"{EmojiTravelAndPlaces.Fire} {EmojiPeopleAndBody.ThumbsUp} {EmojiSmileysAndEmotion.RedHeart}");
```
Shortcodes are recognized by any method that parses markup — `Write`, `WriteLine`, `WriteFormat`,
`Banner`, and `Dash`. See the [Markup guide](markup.md#emoji) for how emoji combine with color tags.
-> Prefer discovering emoji by group? The same constants are also grouped as `Emoji.Group.Name` —
-> see [Group-typed constants](#group-typed-constants).
+> See [Strongly-typed constants](#strongly-typed-constants) below for the full list of per-group
+> classes.
---
@@ -183,71 +183,64 @@ accepted, so all of these are equivalent:
## Strongly-typed constants
-Every emoji is a `public const string` on the static `Emoji` class, so you get IntelliSense,
-compile-time safety, and no parsing overhead:
+Every emoji is a `public const string`, organized into **one public static class per Unicode
+group** — there is no single flat "all emoji" class; the type that backs all of them internally
+(`Emoji`) is an implementation detail and isn't part of the public API. You get IntelliSense,
+compile-time safety, and no parsing overhead by referencing the group class directly:
```csharp
using ConsolePlusLibrary;
-string status = $"{Emoji.CheckMarkButton} Done";
-string alert = $"{Emoji.Warning} Low disk space";
-string love = Emoji.RedHeart;
+string status = $"{EmojiSymbols.CheckMarkButton} Done";
+string alert = $"{EmojiSymbols.Warning} Low disk space";
+string love = EmojiSmileysAndEmotion.RedHeart;
```
-The `Emoji` type is a single `public static partial class`; the constants are simply organized into
-one source file per Unicode group for maintainability. Each constant carries an XML-doc summary and
-its canonical `Lookup:` shortcode, so hovering a constant shows both the glyph and the shortcode.
+Each constant carries an XML-doc summary and its canonical `Lookup:` shortcode, so hovering a
+constant shows both the glyph and the shortcode.
| Shortcode | Constant | Glyph |
|-----------|----------|-------|
-| `:rocket:` | `Emoji.Rocket` | 🚀 |
-| `:fire:` | `Emoji.Fire` | 🔥 |
-| `:thumbs_up:` | `Emoji.ThumbsUp` | 👍 |
-| `:red_heart:` | `Emoji.RedHeart` | ❤️ |
-| `:check_mark_button:` | `Emoji.CheckMarkButton` | ✅ |
-| `:cross_mark:` | `Emoji.CrossMark` | ❌ |
-| `:warning:` | `Emoji.Warning` | ⚠️ |
+| `:rocket:` | `EmojiTravelAndPlaces.Rocket` | 🚀 |
+| `:fire:` | `EmojiTravelAndPlaces.Fire` | 🔥 |
+| `:thumbs_up:` | `EmojiPeopleAndBody.ThumbsUp` | 👍 |
+| `:red_heart:` | `EmojiSmileysAndEmotion.RedHeart` | ❤️ |
+| `:check_mark_button:` | `EmojiSymbols.CheckMarkButton` | ✅ |
+| `:cross_mark:` | `EmojiSymbols.CrossMark` | ❌ |
+| `:warning:` | `EmojiSymbols.Warning` | ⚠️ |
---
## Group-typed constants
-For discoverability, every emoji is **also** exposed as a constant on a nested class named after its
-Unicode group, so you can write `Emoji.Group.Name`. This is the compile-time mirror of the
-[group-qualified shortcode](#group-qualified-shortcodes) form `:group/name:`:
+The per-group class name mirrors the [group-qualified shortcode](#group-qualified-shortcodes) form
+`:group/name:` — pick the class matching the group, then the constant matching the name:
```csharp
using ConsolePlusLibrary;
-// Group-typed constant ↔ group-qualified shortcode
-ConsolePlus.WriteLine($"{Emoji.Activities.Balloon}"); // :activities/balloon: → 🎈
-ConsolePlus.WriteLine($"{Emoji.Symbols.CheckMarkButton}"); // :symbols/check_mark_button: → ✅
-ConsolePlus.WriteLine($"{Emoji.TravelAndPlaces.Rocket}"); // :travel-and-places/rocket: → 🚀
+// Group class constant ↔ group-qualified shortcode
+ConsolePlus.WriteLine($"{EmojiActivities.Balloon}"); // :activities/balloon: → 🎈
+ConsolePlus.WriteLine($"{EmojiSymbols.CheckMarkButton}"); // :symbols/check_mark_button: → ✅
+ConsolePlus.WriteLine($"{EmojiTravelAndPlaces.Rocket}"); // :travel-and-places/rocket: → 🚀
```
-Each nested constant is a **compile-time alias** of the flat constant (`Emoji.Activities.Balloon`
-literally equals `Emoji.Balloon`), so both forms are interchangeable and there is a single source of
-truth for every glyph. Type `Emoji.` then a group name to let IntelliSense narrow the catalog one
-group at a time.
+Type `Emoji` then the group name to let IntelliSense narrow the catalog to that class.
-The ten group classes use the PascalCase names of the official Unicode groups:
+The ten group classes use the PascalCase names of the official Unicode groups, prefixed with `Emoji`:
| Group class | Example |
|-------------|---------|
-| `Emoji.SmileysAndEmotion` | `Emoji.SmileysAndEmotion.GrinningFace` 😀 |
-| `Emoji.PeopleAndBody` | `Emoji.PeopleAndBody.ThumbsUp` 👍 |
-| `Emoji.Component` | `Emoji.Component.LightSkinTone` 🏻 |
-| `Emoji.AnimalsAndNature` | `Emoji.AnimalsAndNature.Dog` 🐶 |
-| `Emoji.FoodAndDrink` | `Emoji.FoodAndDrink.Pizza` 🍕 |
-| `Emoji.TravelAndPlaces` | `Emoji.TravelAndPlaces.Rocket` 🚀 |
-| `Emoji.Activities` | `Emoji.Activities.Balloon` 🎈 |
-| `Emoji.Objects` | `Emoji.Objects.Laptop` 💻 |
-| `Emoji.Symbols` | `Emoji.Symbols.CheckMarkButton` ✅ |
-| `Emoji.Flags` | `Emoji.Flags.ChequeredFlag` 🏁 |
-
-> The flat form (`Emoji.Rocket`) and the group-typed form (`Emoji.TravelAndPlaces.Rocket`) always
-> resolve to the same value; pick whichever reads better in your code. Legacy aliases remain on the
-> flat `Emoji` class only.
+| `EmojiSmileysAndEmotion` | `EmojiSmileysAndEmotion.GrinningFace` 😀 |
+| `EmojiPeopleAndBody` | `EmojiPeopleAndBody.ThumbsUp` 👍 |
+| `EmojiComponent` | `EmojiComponent.LightSkinTone` 🏻 |
+| `EmojiAnimalsAndNature` | `EmojiAnimalsAndNature.Dog` 🐶 |
+| `EmojiFoodAndDrink` | `EmojiFoodAndDrink.Pizza` 🍕 |
+| `EmojiTravelAndPlaces` | `EmojiTravelAndPlaces.Rocket` 🚀 |
+| `EmojiActivities` | `EmojiActivities.Balloon` 🎈 |
+| `EmojiObjects` | `EmojiObjects.Laptop` 💻 |
+| `EmojiSymbols` | `EmojiSymbols.CheckMarkButton` ✅ |
+| `EmojiFlags` | `EmojiFlags.ChequeredFlag` 🏁 |
---
@@ -277,14 +270,20 @@ Each group has its own reference page listing every glyph, constant, and shortco
## Legacy aliases
-A few older names are preserved as aliases so existing code keeps working. They resolve to the
-current canonical emoji:
+A few older names are preserved as **shortcode** aliases so existing text using them keeps
+resolving to the current canonical emoji when parsed as markup:
-| Alias | Shortcode | Resolves to |
-|-------|-----------|-------------|
-| `Emoji.HuggingFace` | `:hugging_face:` | `Emoji.SmilingFaceWithOpenHands` 🤗 |
-| `Emoji.KnockedOutFace` | `:knocked_out_face:` | `Emoji.FaceWithCrossedOutEyes` 😵 |
-| `Emoji.PoutingFace` | `:pouting_face:` | `Emoji.EnragedFace` 😡 |
+| Alias shortcode | Resolves to | Glyph |
+|-------|-----------|-------|
+| `:hugging_face:` | `SmilingFaceWithOpenHands` | 🤗 |
+| `:knocked_out_face:` | `FaceWithCrossedOutEyes` | 😵 |
+| `:pouting_face:` | `EnragedFace` | 😡 |
+
+> These aliases only exist as shortcodes today — the constants backing them live on the internal
+> `Emoji` type, not on any of the public per-group classes above, so there is currently no
+> strongly-typed way to reference `HuggingFace`/`KnockedOutFace`/`PoutingFace` in code. Use the
+> canonical name's public constant instead (e.g. `EmojiSmileysAndEmotion.SmilingFaceWithOpenHands`),
+> or the `:hugging_face:` shortcode form.
---
@@ -309,15 +308,13 @@ for detection details and how to override the profile if needed.
// Group-qualified shortcode (prefix validated against the 10 groups)
":activities/balloon:" ":symbols/check_mark_button:"
-// Strongly-typed constant
-Emoji.Rocket Emoji.RedHeart Emoji.CheckMarkButton
-
-// Group-typed constant (Emoji.Group.Name)
-Emoji.Activities.Balloon Emoji.Symbols.CheckMarkButton Emoji.TravelAndPlaces.Rocket
+// Strongly-typed constant — one public class per group
+EmojiTravelAndPlaces.Rocket EmojiSmileysAndEmotion.RedHeart EmojiSymbols.CheckMarkButton
+EmojiActivities.Balloon
// Combine with markup
"[Green]:check_mark_button: Success[/]"
-$"[Red]{Emoji.CrossMark} Failed[/]"
+$"[Red]{EmojiSymbols.CrossMark} Failed[/]"
```
Jump to a group: [Smileys & Emotion](emoji/smileys-and-emotion.md) •
diff --git a/docs/environment-detection.md b/docs/environment-detection.md
index 816c4ab..c77bf6c 100644
--- a/docs/environment-detection.md
+++ b/docs/environment-detection.md
@@ -171,9 +171,10 @@ detects this and enables:
- Text styling (bold, underline, etc.)
- Screen buffer control
-On **legacy Windows** (pre-Windows 10), ConsolePlus automatically injects
-**[ANSICON](https://github.com/adoxa/ansicon)** (bundled) using `LdrLoadDll` via `CreateRemoteThread`
-to provide transparent ANSI support.
+On **legacy Windows** (pre-Windows 10), ConsolePlus automatically launches the bundled
+**[ANSICON](https://github.com/adoxa/ansicon)** executable (`ansicon.exe -p`, matching the process
+architecture) via `Process.Start` — no DLL injection involved — to provide transparent ANSI
+support.
### Unicode support
@@ -201,25 +202,40 @@ Colors are automatically down-sampled if the terminal doesn't support the reques
## Override detection
-You can override the automatic detection if needed by modifying the profile after initialization:
+`ConsolePlus.Profile` is a **read-only snapshot** — there is no supported way to mutate it in code
+after initialization. To override auto-detection, drop a `ConsoleProfile.json` file next to your
+application's executable (same folder as `AppContext.BaseDirectory`); ConsolePlus reads it once,
+**before** any detection runs, so overridden values take full effect (unlike a hypothetical
+post-init mutation, which would arrive too late — several detected values are already cached
+elsewhere in the console pipeline by the time your own code could run):
-```csharp
-using ConsolePlusLibrary;
-using ConsolePlusLibrary.Core;
-
-// Force disable ANSI even if detected
-ConsolePlus.Profile.SupportsAnsi = AutoDetect.No;
-
-// Force specific color depth
-ConsolePlus.Profile.ColorDepth = ColorSystem.FourBit;
-
-// Force Unicode support
-ConsolePlus.Profile.SupportUnicode = AutoDetect.Yes;
-
-// Mark as interactive
-ConsolePlus.Profile.Interactive = true;
+```json
+{
+ "SupportsAnsi": "No",
+ "ColorDepth": "FourBit",
+ "SupportUnicode": "Yes",
+ "Interactive": "Detect",
+ "OriginalCulture": "Detect",
+ "DefaultInputEncoding": "Detect",
+ "DefaultOutputEncoding": "Detect",
+ "DefaultBackgroundColor": "Detect",
+ "DefaultForegroundColor": "Detect",
+ "ProfileName": "Detect",
+ "IsTerminal": "Detect"
+}
```
+- `SupportsAnsi` / `SupportUnicode` — `"Yes"`, `"No"`, or `"Detect"` (keeps auto-detection).
+- `ColorDepth` — `"NoColors"`, `"FourBit"`, `"Standard"`, or `"TrueColor"` (any other value keeps
+ auto-detection).
+- `Interactive` / `IsTerminal` — `"true"`/`"false"`, or `"Detect"`.
+- `DefaultBackgroundColor` / `DefaultForegroundColor` — a hex string (e.g. `"#1E90FF"`), or
+ `"Detect"`.
+- `DefaultInputEncoding` / `DefaultOutputEncoding` — a .NET encoding name (e.g. `"utf-8"`), or
+ `"Detect"`.
+- `OriginalCulture` / `ProfileName` — any string, or `"Detect"`.
+- Only include the keys you actually want to override; omit or use `"Detect"` for the rest.
+
> ⚠️ **Warning:** Overriding detection should be done carefully, as incorrect settings may result
> in garbled output or missing features. Use this only when you have specific knowledge about your
> deployment environment that the automatic detection cannot infer.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 0c94c1a..bbd343b 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -129,8 +129,8 @@ ConsolePlus initializes **automatically** the first time you touch the `ConsoleP
1. **Attempts to enable ANSI** on legacy Windows versions (using a bundled helper when needed).
- On legacy Windows systems (pre-Windows 10) that lack native ANSI support, ConsolePlus
automatically uses **[ANSICON](https://github.com/adoxa/ansicon)** (bundled with the library)
- - The injection uses the `LdrLoadDll` approach via `CreateRemoteThread` for 64-bit .NET AnyCPU
- processes
+ - It does this by launching the bundled `ansicon.exe` (matching the process architecture,
+ x86 or x64) via `Process.Start(..., "-p")` — no DLL injection involved
- This provides transparent ANSI escape sequence support without requiring manual installation
or configuration
2. **Captures the original console state** — culture, foreground/background colors, and input/output
@@ -159,11 +159,12 @@ ConsolePlus registers process-exit handlers so your terminal is **left in a clea
original colors, culture, and encodings are restored automatically, and streams are flushed.
You can also register your own callback to run just before the process exits — for example, to
-print a farewell message or persist state. The callback receives the console instance and a flag
-indicating whether the exit was triggered by Ctrl+C:
+print a farewell message or persist state. The callback receives the console instance, any
+exception that caused the exit (`null` on a normal exit), and a flag indicating whether the exit
+was triggered by Ctrl+C:
```csharp
-ConsolePlus.ActionBeforeExist((console, ctrlCPressed) =>
+ConsolePlus.ActionBeforeExit((console, exception, ctrlCPressed) =>
{
console.WriteLine(ctrlCPressed
? "[Red]Cancelled by user.[/]"
diff --git a/docs/markup.md b/docs/markup.md
index 6f99df1..4f49cba 100644
--- a/docs/markup.md
+++ b/docs/markup.md
@@ -186,8 +186,8 @@ You can mix explicit `[/]` closings with automatic ones:
ConsolePlus.WriteLine("[Green]Start [Red]Middle[/] End");
// Multiple levels with partial explicit closing
-ConsolePlus.WriteLine("[A]Level1 [B]Level2 [C]Level3[/] Back to B");
-// Level3 closed explicitly, Level2 and Level1 close automatically
+ConsolePlus.WriteLine("[Red]Level1 [Green]Level2 [Blue]Level3[/] Back to Green");
+// Level3 (Blue) closed explicitly, Level2 (Green) and Level1 (Red) close automatically
```
> 💡 **Best practice:** Use explicit `[/]` when you need precise control over where colors change,
@@ -230,15 +230,16 @@ ConsolePlus.WriteLine("[Red:Blue:Green]Invalid format");
// Output: "[Red:Blue:Green]Invalid format" (literal text, no colors)
ConsolePlus.WriteLine("[NotAColor]Unknown color name");
-// Output: "[NotAColor]Unknown color name" (literal text, no colors)
-
-ConsolePlus.WriteLine("Text [/] with unexpected closing");
-// Output: "Text [/] with unexpected closing" (literal text, no colors)
-
-ConsolePlus.WriteLine("[RGB(999,999,999)]Out of range");
-// Output: "[RGB(999,999,999)]Out of range" (literal text, no colors)
+// Output: "[NotAColor]Unknown color name" — only this unresolved tag becomes literal text;
+// text after it keeps parsing normally under the surrounding/inherited style, if any.
```
+> ⚠️ Two cases that look like errors but are **not**: an unmatched `[/]` (no open tag left on the
+> stack) is silently dropped rather than shown as literal text — `"Text [/] with no opener"` renders
+> as `"Text with no opener"` (the tag just vanishes). And out-of-range RGB components
+> (`[RGB(999,999,999)]`) do **not** raise an error or fall back to raw text — they're cast to `byte`
+> unchecked, so `999` silently wraps to `231` and a valid (if unexpected) color is applied.
+
### Why raw output?
Rendering as raw text when errors occur has several benefits:
@@ -253,24 +254,30 @@ Rendering as raw text when errors occur has several benefits:
| Error Type | Example | Result |
|------------|---------|--------|
| **Invalid color format** | `[Red:Blue:Green]` | Raw text (too many colors) |
-| **Unknown color name** | `[NotARealColor]` | Raw text (color doesn't exist) |
-| **Malformed RGB** | `[RGB(256,300,500)]` | Raw text (values out of range) |
+| **Unknown color name** | `[NotARealColor]` | The unresolved tag is shown as literal text; surrounding text is unaffected |
| **Invalid HEX** | `[#GGGGGG]` | Raw text (invalid hex digits) |
-| **Unbalanced brackets** | `[Red][Green]text[/]` | Raw text (mismatched tags) |
| **Empty tags** | `[]text[/]` | Raw text (no color specified) |
+> `[Red][Green]text[/]` is **not** an error — it's valid nested markup (`Green` closes explicitly,
+> `Red` auto-closes at the end); see [Nesting and hierarchy](#nesting-and-hierarchy) above.
+> Out-of-range RGB (`[RGB(256,300,500)]`) also does **not** trigger raw output — see the warning
+> above this table's section.
+
### Fault-tolerant features
-Despite the raw output fallback, ConsolePlus markup is designed to be forgiving:
+Despite the raw output fallback, ConsolePlus markup is designed to be forgiving in some cases —
+but not as broadly as it may first appear:
```csharp
// These work fine - parser is lenient
ConsolePlus.WriteLine("[red]Case insensitive[/]"); // ✅ Works
-ConsolePlus.WriteLine("[Red ]Extra space[/]"); // ✅ Works
-ConsolePlus.WriteLine("[ Red ]Extra spaces[/]"); // ✅ Works
ConsolePlus.WriteLine("[Red]Auto-close"); // ✅ Works (auto-closes)
ConsolePlus.WriteLine("[Red on White]Background[/]"); // ✅ Works
ConsolePlus.WriteLine("[Red:White]Shorter syntax[/]"); // ✅ Works
+
+// These do NOT work despite looking like reasonable whitespace tolerance
+ConsolePlus.WriteLine("[Red ]Extra space[/]"); // ❌ Falls back to raw text
+ConsolePlus.WriteLine("[ Red ]Extra spaces[/]"); // ❌ Tag rendered literally, not colored
```
### Testing markup before use
@@ -294,14 +301,18 @@ ConsolePlus.WriteLine($"User said: {safe}");
### Best practices for robust markup
1. **Validate dynamic colors** — If building markup from variables, validate color values first
-2. **Use constants** — Prefer the `Color` class constants over string literals
+2. **Use constants** — Prefer the `Color` class constants over string literals, and interpolate
+ them with `.ToMarkup()`, not `.ToString()` — for a color with no exact CSS name (e.g. a custom
+ `new Color(r,g,b)`), `ToString()` returns `"#RRGGBB (RGB=r,g,b)"` (with a space and parentheses),
+ which breaks the tag when interpolated; `ToMarkup()` always returns a tag-safe form
+ (`"#RRGGBB"` or the CSS name)
3. **Test in development** — Run your output once to verify markup renders correctly
4. **Escape user input** — Always `.EscapeMarkup()` on any user-provided strings
5. **Keep it simple** — Complex nested markup is harder to debug; consider using `Style` objects instead
```csharp
-// Good - using Color constants
-ConsolePlus.WriteLine($"[{Color.Red}]Error[/]");
+// Good - using Color.ToMarkup(), not ToString()
+ConsolePlus.WriteLine($"[{Color.Red.ToMarkup()}]Error[/]");
// Good - escaping user input
string fileName = userInput.EscapeMarkup();
@@ -330,27 +341,26 @@ ConsolePlus.WriteLine("[Green]:check_mark_button: Success[/]");
ConsolePlus.WriteLine(":fire: :thumbs_up: :red_heart:");
```
-You can also reference emoji as **strongly-typed constants** from the `Emoji` class:
+You can also reference emoji as **strongly-typed constants**, grouped into one public static class
+per Unicode emoji group (`EmojiActivities`, `EmojiSymbols`, `EmojiTravelAndPlaces`, …):
```csharp
using ConsolePlusLibrary;
-ConsolePlus.WriteLine($"{Emoji.Rocket} Launching...");
-ConsolePlus.WriteLine($"{Emoji.Fire} {Emoji.ThumbsUp} {Emoji.RedHeart}");
-
-// Or discover them by group with Emoji.Group.Name
-ConsolePlus.WriteLine($"{Emoji.TravelAndPlaces.Rocket} {Emoji.Symbols.CheckMarkButton}");
+ConsolePlus.WriteLine($"{EmojiTravelAndPlaces.Rocket} Launching...");
+ConsolePlus.WriteLine($"{EmojiTravelAndPlaces.Fire} {EmojiPeopleAndBody.ThumbsUp} {EmojiSmileysAndEmotion.RedHeart}");
+ConsolePlus.WriteLine($"{EmojiSymbols.CheckMarkButton} Success");
```
| Shortcode | Constant | Glyph |
|-----------|----------|-------|
-| `:rocket:` | `Emoji.Rocket` | 🚀 |
-| `:fire:` | `Emoji.Fire` | 🔥 |
-| `:thumbs_up:` | `Emoji.ThumbsUp` | 👍 |
-| `:red_heart:` | `Emoji.RedHeart` | ❤️ |
-| `:check_mark_button:` | `Emoji.CheckMarkButton` | ✅ |
-| `:cross_mark:` | `Emoji.CrossMark` | ❌ |
-| `:warning:` | `Emoji.Warning` | ⚠️ |
+| `:rocket:` | `EmojiTravelAndPlaces.Rocket` | 🚀 |
+| `:fire:` | `EmojiTravelAndPlaces.Fire` | 🔥 |
+| `:thumbs_up:` | `EmojiPeopleAndBody.ThumbsUp` | 👍 |
+| `:red_heart:` | `EmojiSmileysAndEmotion.RedHeart` | ❤️ |
+| `:check_mark_button:` | `EmojiSymbols.CheckMarkButton` | ✅ |
+| `:cross_mark:` | `EmojiSymbols.CrossMark` | ❌ |
+| `:warning:` | `EmojiSymbols.Warning` | ⚠️ |
> ⚠️ **Important:** Emoji require Unicode support to display correctly. On terminals without Unicode
> or emoji-capable fonts, emoji will appear as monochrome symbols, placeholder boxes, or be silently
@@ -360,8 +370,9 @@ ConsolePlus.WriteLine($"{Emoji.TravelAndPlaces.Rocket} {Emoji.Symbols.CheckMarkB
You can optionally prefix a shortcode with its Unicode group, for example
`:activities/balloon:`. See [Emoji → Group-qualified shortcodes](emoji.md#group-qualified-shortcodes)
-for the full list of groups and the complete catalog. The same grouping is available on the `Emoji`
-class as [group-typed constants](emoji.md#group-typed-constants) (`Emoji.Activities.Balloon`).
+for the full list of groups and the complete catalog. The same grouping is available as
+[group-typed constants](emoji.md#group-typed-constants) — one public class per group
+(`EmojiActivities.Balloon`, not a nested type).
---
@@ -416,7 +427,7 @@ rather than the raw string length.
value.EscapeMarkup()
// Emoji
-":rocket:" Emoji.Rocket Emoji.TravelAndPlaces.Rocket
+":rocket:" EmojiTravelAndPlaces.Rocket
// Helpers
text.RemoveMarkup() text.LengthMarkup() text.EscapeMarkup()
diff --git a/docs/profiles-and-capabilities.md b/docs/profiles-and-capabilities.md
index 3f43d07..551edeb 100644
--- a/docs/profiles-and-capabilities.md
+++ b/docs/profiles-and-capabilities.md
@@ -35,8 +35,9 @@ The static initializer runs **once**, the first time you touch `ConsolePlus`, an
- Modern Windows (Windows 10+) has native ANSI support via Virtual Terminal Processing
- On legacy Windows systems (pre-Windows 10) that lack native ANSI support, ConsolePlus
automatically uses **[ANSICON](https://github.com/adoxa/ansicon)** (bundled with the library)
- - The ANSICON injection uses the `LdrLoadDll` approach via `CreateRemoteThread` for 64-bit
- .NET AnyCPU processes, providing transparent ANSI escape sequence support
+ - It does this by launching the bundled `ansicon.exe` (matching the process architecture,
+ x86 or x64) via `Process.Start(..., "-p")` — no DLL injection involved, and it provides
+ transparent ANSI escape sequence support
- This happens automatically during initialization without requiring manual installation or
configuration
2. **Captures the original culture, colors, and input/output encodings** (so they can be restored on
@@ -81,6 +82,10 @@ ConsolePlus.WriteLine($"ColorDepth : {p.ColorDepth}"); // ColorSystem
| `DefaultInputEncoding` / `DefaultOutputEncoding` | `Encoding` | Default encodings |
| `OriginalInputEncoding` / `OriginalOutputEncoding` | `Encoding` | Encodings captured at startup |
+`IProfileReadOnly` is genuinely **read-only** — there is no fluent setter API on it or on
+`ConsolePlus.Profile`. To override any of these values, use a `ConsoleProfile.json` file, read once
+before detection runs; see [Environment Detection → Override detection](environment-detection.md#override-detection).
+
---
## Capability quick-checks
@@ -167,7 +172,7 @@ always restored to its original culture, colors, and encodings when your app end
shutdown to run final logic:
```csharp
-ConsolePlus.ActionBeforeExist((console, ctrlCPressed) =>
+ConsolePlus.ActionBeforeExit((console, exception, ctrlCPressed) =>
{
console.WriteLine(ctrlCPressed
? "[Yellow]Cancelled by user (Ctrl+C).[/]"
@@ -175,7 +180,8 @@ ConsolePlus.ActionBeforeExist((console, ctrlCPressed) =>
});
```
-The callback receives the console instance and a `bool` indicating whether Ctrl+C triggered the exit.
+The callback receives the console instance, any exception that caused the exit (`null` on a
+normal exit), and a `bool` indicating whether Ctrl+C triggered the exit.
---
@@ -222,7 +228,7 @@ bool outRedirected = ConsolePlus.IsOutputRedirected;
bool inRedirected = ConsolePlus.IsInputRedirected;
// Graceful shutdown hook
-ConsolePlus.ActionBeforeExist((console, ctrlC) => console.WriteLine("Bye!"));
+ConsolePlus.ActionBeforeExit((console, exception, ctrlC) => console.WriteLine("Bye!"));
```
---
diff --git a/docs/reading-input.md b/docs/reading-input.md
index bd94602..829b37c 100644
--- a/docs/reading-input.md
+++ b/docs/reading-input.md
@@ -103,8 +103,9 @@ string ReadLineEmacs(
### Key bindings
While editing, the following Emacs-style shortcuts are available. Most of them also have an
-equivalent standard editing key. Case-changing, deletion, and clearing commands only take effect
-when there is text in the buffer.
+equivalent standard editing key. **Every shortcut below — including plain cursor movement, not just
+editing/deletion/case-changing — only takes effect when there is text in the buffer** (an empty
+buffer ignores `Home`/`End`/arrows/`Ctrl+A`/`Ctrl+E`/etc. entirely, not just deletion commands).
**Cursor movement**
@@ -211,6 +212,13 @@ string code = ConsolePlus.ReadLineEmacs(
| `Uppercase` | Converts accepted characters to uppercase |
| `Lowercase` | Converts accepted characters to lowercase |
+> ⚠️ **Order matters when combining `caseOptions` with `acceptInput`.** The case transform runs
+> **before** `acceptInput` sees the character — `acceptInput` receives the already-transformed
+> character, not the one the user actually typed. A predicate that only accepts one case (e.g.
+> `caseOptions: CaseOptions.Uppercase` combined with `acceptInput: char.IsLower`) will silently
+> reject every character, since it never sees a lowercase char to accept. Write `acceptInput`
+> predicates that are case-insensitive (or that expect the target case) when using `caseOptions`.
+
### Async and cancellation
The async variants accept a `CancellationToken` and return `null` if canceled:
@@ -250,6 +258,11 @@ while (!ConsolePlus.KeyAvailable)
var key = ConsolePlus.ReadKey(intercept: true);
```
+> ⚠️ `ReadKey`/`ReadKeyAsync` throw `InvalidOperationException` ("Console is not interactive.")
+> when `ConsolePlus.Profile.Interactive` is `false` — for example, when input is redirected. Check
+> `IsInputRedirected` (or `Profile.Interactive`) before calling them in code that might run with
+> redirected/piped input.
+
---
## Cheat sheet
diff --git a/docs/styles.md b/docs/styles.md
index fc4a122..b7dbc8e 100644
--- a/docs/styles.md
+++ b/docs/styles.md
@@ -58,7 +58,7 @@ Use them anywhere output is written:
```csharp
ConsolePlus.WriteLine("Hello", new Style(Color.Black, Color.Gold));
-ConsolePlus.Banner("Title", new Style(Color.White, Color.DarkBlue), DashOptions.SingleBorder);
+ConsolePlus.Banner("Title", new Style(Color.White, Color.Darkblue), DashOptions.SingleBorder);
```
---
@@ -152,7 +152,7 @@ void Log(string level, Color color, string message)
ConsolePlus.WriteLine(message, ConsolePlus.CurrentStyle.Overflow(Overflow.Ellipsis));
}
-Log("INFO", Color.SteelBlue, "Service started");
+Log("INFO", Color.Steelblue, "Service started");
Log("WARN", Color.Gold, "Cache miss rate is high");
Log("ERR ", Color.Firebrick, "Connection reset by peer");
```
diff --git a/docs/widgets.md b/docs/widgets.md
index 4fe3bc5..0e39f93 100644
--- a/docs/widgets.md
+++ b/docs/widgets.md
@@ -107,10 +107,12 @@ ConsolePlus.Dash(
## Banners
-A **banner** draws large, attention-grabbing text. Without a FIGlet font it renders the text on a
-single line (optionally bordered); with a font it renders multi-line ASCII-art letters.
+A **banner** draws large, attention-grabbing text as multi-line [FIGlet](http://www.figlet.org/)
+ASCII-art letters. Every overload renders FIGlet art, even without an explicit font argument — the
+library embeds the **Standard** FIGlet font as the default, so there is no "plain single-line text"
+banner mode.
-### Simple banners
+### Banner with the default (embedded) font
```csharp
using ConsolePlusLibrary;
@@ -128,10 +130,10 @@ ConsolePlus.Banner(
DashOptions dashOptions = DashOptions.None);
```
-### FIGlet banners
+### Banner with a custom font
-Provide a [FIGlet](http://www.figlet.org/) font (a `.flf` file path or a `Stream`) to render
-stylized letter art:
+Provide a [FIGlet](http://www.figlet.org/) font (a `.flf` file path or a `Stream`) to render with a
+different letter style than the embedded default:
```csharp
// From a font file on disk
@@ -152,9 +154,8 @@ Example (FIGlet "Standard" font):
|_| |_|\___|_|_|\___/
```
-> The library embeds the **Standard** FIGlet font, which is used for simple banners. For custom
-> looks, supply any `.flf` font file or stream. Invalid/missing font paths throw an
-> `ArgumentException`.
+> The library embeds this **Standard** FIGlet font as the default. For a different look, supply any
+> `.flf` font file or stream. Invalid/missing font paths throw an `ArgumentException`.
---
diff --git a/samples/ConsolePlusFeaturesSamples/ConsolePlusFeaturesSamples.csproj b/samples/ConsolePlusFeaturesSamples/ConsolePlusFeaturesSamples.csproj
index d97eff3..2fcea18 100644
--- a/samples/ConsolePlusFeaturesSamples/ConsolePlusFeaturesSamples.csproj
+++ b/samples/ConsolePlusFeaturesSamples/ConsolePlusFeaturesSamples.csproj
@@ -6,6 +6,7 @@
enable
enable
1.0.0
+ Debug;Release;ReleaseDoc
diff --git a/src/ConsoleAbstractions/AnsiConsoleAdapter.cs b/src/ConsoleAbstractions/AnsiConsoleAdapter.cs
index 433ea19..1f73b54 100644
--- a/src/ConsoleAbstractions/AnsiConsoleAdapter.cs
+++ b/src/ConsoleAbstractions/AnsiConsoleAdapter.cs
@@ -1107,15 +1107,15 @@ public bool HideCursor()
{
_writer.Ansi.HideCursor();
}
- catch (PlatformNotSupportedException)
+ catch (Exception ex) when (ex is PlatformNotSupportedException or IOException)
{
- // Ignore if the platform doesn't support this operation
- }
- finally
- {
- _cursorVisible = false;
+ // Ignore if the platform doesn't support this operation, or there is no real
+ // console attached (e.g. headless/redirected process) — same "Safe" pattern
+ // used by EnvironmentUtil.GetSafeWidth/GetSafeHeight/GetSafeTopCursor/etc.
+ return false;
}
- return _cursorVisible;
+ _cursorVisible = false;
+ return true;
});
}
@@ -1129,15 +1129,15 @@ public bool ShowCursor()
{
_writer.Ansi.ShowCursor();
}
- catch (PlatformNotSupportedException)
+ catch (Exception ex) when (ex is PlatformNotSupportedException or IOException)
{
- // Ignore if the platform doesn't support this operation
- }
- finally
- {
- _cursorVisible = true;
+ // Ignore if the platform doesn't support this operation, or there is no real
+ // console attached (e.g. headless/redirected process) — same "Safe" pattern
+ // used by EnvironmentUtil.GetSafeWidth/GetSafeHeight/GetSafeTopCursor/etc.
+ return false;
}
- return _cursorVisible;
+ _cursorVisible = true;
+ return true;
});
}
diff --git a/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs b/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs
index 95bb68d..277a831 100644
--- a/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs
+++ b/src/ConsoleAbstractions/NoAnsiConsoleAdapter.cs
@@ -1098,11 +1098,15 @@ public bool HideCursor()
{
Console.CursorVisible = false;
}
- finally
+ catch (Exception ex) when (ex is PlatformNotSupportedException or IOException)
{
- _cursorVisible = false;
+ // Ignore if the platform doesn't support this operation, or there is no real
+ // console attached (e.g. headless/redirected process) — same "Safe" pattern
+ // used by EnvironmentUtil.GetSafeWidth/GetSafeHeight/GetSafeTopCursor/etc.
+ return false;
}
- return _cursorVisible;
+ _cursorVisible = false;
+ return true;
});
}
@@ -1116,15 +1120,15 @@ public bool ShowCursor()
{
Console.CursorVisible = true;
}
- catch (PlatformNotSupportedException)
+ catch (Exception ex) when (ex is PlatformNotSupportedException or IOException)
{
- // Ignore if the platform doesn't support this operation
+ // Ignore if the platform doesn't support this operation, or there is no real
+ // console attached (e.g. headless/redirected process) — same "Safe" pattern
+ // used by EnvironmentUtil.GetSafeWidth/GetSafeHeight/GetSafeTopCursor/etc.
+ return false;
}
- finally
- {
- _cursorVisible = true;
- }
- return _cursorVisible;
+ _cursorVisible = true;
+ return true;
});
}
diff --git a/src/ConsolePlus.Extends.cs b/src/ConsolePlus.Extends.cs
index a159da6..8f93c6e 100644
--- a/src/ConsolePlus.Extends.cs
+++ b/src/ConsolePlus.Extends.cs
@@ -67,7 +67,6 @@ public static void ClearLine(this IConsole console, int? row = null, Style? styl
{
ConsolePlus.Envlock.Run(() =>
{
- console.ClearLine(row, style);
row ??= console.CursorTop;
var currentstyle = console.CurrentStyle;
var effectiveStyle = style ?? currentstyle;
diff --git a/src/ConsolePlus.csproj b/src/ConsolePlus.csproj
index cda97e5..1b129bd 100644
--- a/src/ConsolePlus.csproj
+++ b/src/ConsolePlus.csproj
@@ -11,6 +11,7 @@
latest-recommended
$(DefaultItemExcludes);ExternalAnsiBundleLegacy\**;build\**
+ Debug;Release;ReleaseDoc
@@ -93,7 +94,7 @@
-
+
@@ -107,7 +108,7 @@
-
+
..\docs\api
Assembly, Namespaces , Classes, Interfaces, Events, Enums, Structs, Delegates
NameAndMd5Mix
@@ -145,7 +146,7 @@
-
+
<_DocIconFolder>$([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(DefaultDocumentationFolder)'))))
diff --git a/src/Shared/IProfileSetup.cs b/src/Shared/IProfileSetup.cs
deleted file mode 100644
index d2aa8ec..0000000
--- a/src/Shared/IProfileSetup.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-// ***************************************************************************************
-// MIT LICENCE
-// The maintenance and evolution is maintained by the ConsolePlus project under MIT license
-// ***************************************************************************************
-
-using System.Text;
-#pragma warning disable IDE0130 // Namespace does not match folder structure
-namespace ConsolePlusLibrary
-#pragma warning restore IDE0130 // Namespace does not match folder structure
-{
- ///
- /// Interface for configuring console profiles, allowing for the setup of various console settings such as encoding, color, and interaction capabilities.
- ///
- public interface IProfileSetup
- {
- ///
- /// Default input encoding of the console at the time the profile was created.
- ///
- /// The encoding to set as the default input encoding.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup DefaultInputEncoding(Encoding value);
-
- ///
- /// Default output encoding of the console at the time the profile was created.
- ///
- /// The encoding to set as the default output encoding.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup DefaultOutputEncoding (Encoding value);
-
- ///
- /// Default background used when no explicit color is specified.
- ///
- /// The color to set as the default background color.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup DefaultBackgroundColor(Color value);
-
- ///
- ///Indicating whether or not the console supports interaction.
- ///
- /// A boolean value indicating whether the console supports interaction.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup Interactive(bool value);
-
- ///
- /// Indicates whether Unicode output is fully supported.
- ///
- /// An value indicating whether Unicode output is supported.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup SupportUnicode(AutoDetect value);
-
- ///
- /// Gets a value indicating whether ANSI escape sequences are supported for styling/output.
- ///
- /// An value indicating whether ANSI escape sequences are supported.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup SupportsAnsi(AutoDetect value);
-
- ///
- /// Color depth (capability) of the console.
- ///
- /// The color depth to set for the console.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup ColorDepth(ColorSystem value);
-
-
- ///
- /// Time taken for the console to trigger a resize event after the console window has been resized.
- /// This can be used to optimize performance by reducing the frequency of resize events, especially in scenarios where rapid resizing may occur.
- /// Default value is 300 milliseconds, Range valid 100-1000 milliseconds.
- ///
- /// The time in milliseconds to set for the resize event delay.
- /// The current instance of to allow for fluent configuration.
- IProfileSetup TimeMsResizeChange(int value);
-
- ///
- /// Applies the configured profile settings to the console environment.
- /// This method should be called after all desired settings have been configured using the fluent interface methods.
- ///
- void Apply();
- }
-}
diff --git a/tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj b/tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj
index e0e4df0..eb57b6d 100644
--- a/tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj
+++ b/tests/ConsolePlus.Tests/ConsolePlus.Tests.csproj
@@ -5,6 +5,7 @@
enable
latest
false
+ Debug;Release;ReleaseDoc
diff --git a/tests/ConsolePlus.Tests/Unit/ConsolePlusExtendsTests.cs b/tests/ConsolePlus.Tests/Unit/ConsolePlusExtendsTests.cs
new file mode 100644
index 0000000..631e0aa
--- /dev/null
+++ b/tests/ConsolePlus.Tests/Unit/ConsolePlusExtendsTests.cs
@@ -0,0 +1,53 @@
+using ConsolePlusLibrary;
+using ConsolePlusLibrary.Testing;
+using FluentAssertions;
+using Xunit;
+
+namespace ConsolePlus.Tests.Unit
+{
+ // ConsolePlusExtends.cs — extension methods on IConsole. Ground truth: ClearLine had a real
+ // bug (self-recursive call with no base case, StackOverflowException on any real use) found
+ // during a docs audit; this covers the fix. Touching any method here forces the static
+ // ConsolePlusLibrary.ConsolePlus singleton to initialize for real (Envlock.Run is a static
+ // member access) — that used to crash the whole test host with IOException ("The handle is
+ // invalid") from NoAnsiConsoleAdapter's ctor calling ShowCursor() against a console-less test
+ // process; fixed by making HideCursor/ShowCursor swallow IOException the same way
+ // EnvironmentUtil's other Safe* console helpers already do.
+ //
+ // Real singleton init also runs EnvironmentUtil.CreateProfile -> EnrichersCI(), which populates
+ // the process-wide static BaseClassCI._environmentVariables cache that ProfileExtensionsTests
+ // manages by hand via reflection. Since xUnit parallelizes different test classes by default,
+ // this class must share GlobalStateCollection with ProfileExtensionsTests or the two race on
+ // that cache (confirmed: intermittent CI failure on macOS, 2026-07-25 — ProfileExtensionsTests
+ // saw a stale/real-environment snapshot mid-reset because this class's singleton init ran
+ // concurrently and repopulated the shared cache).
+ [Collection(GlobalStateCollection.Name)]
+ public class ConsolePlusExtendsTests
+ {
+ [Fact]
+ public void ClearLine_blanks_the_current_row_without_recursing()
+ {
+ var vt = VirtualTerminal.Create();
+ vt.SetCursorPosition(0, 0);
+ vt.Write("hello");
+
+ vt.ClearLine();
+
+ vt.TextAt(0, 0, 5).Trim().Should().BeEmpty();
+ }
+
+ [Fact]
+ public void ClearLine_targets_the_given_row_and_restores_the_cursor_style()
+ {
+ var vt = VirtualTerminal.Create();
+ vt.SetCursorPosition(0, 2);
+ vt.Write("world");
+ var styleBefore = vt.CurrentStyle;
+
+ vt.ClearLine(row: 2);
+
+ vt.TextAt(2, 0, 5).Trim().Should().BeEmpty();
+ vt.CurrentStyle.Should().Be(styleBefore);
+ }
+ }
+}
diff --git a/tests/ConsolePlus.Tests/Unit/GlobalStateCollection.cs b/tests/ConsolePlus.Tests/Unit/GlobalStateCollection.cs
new file mode 100644
index 0000000..0627da0
--- /dev/null
+++ b/tests/ConsolePlus.Tests/Unit/GlobalStateCollection.cs
@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace ConsolePlus.Tests.Unit
+{
+ // Serializes test classes that touch process-wide static state that isn't otherwise isolated
+ // per-instance: BaseClassCI's environment-variable cache (ProfileExtensionsTests) and the real
+ // ConsolePlusLibrary.ConsolePlus singleton, whose static ctor populates that same cache
+ // (ConsolePlusExtendsTests). xUnit parallelizes different test classes by default; classes in
+ // this collection run sequentially relative to each other instead.
+ [CollectionDefinition(Name, DisableParallelization = true)]
+ public class GlobalStateCollection
+ {
+ public const string Name = "ConsolePlus global state (BaseClassCI env-var cache / ConsolePlus singleton)";
+ }
+}
diff --git a/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs b/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs
index 2341e69..ffd6048 100644
--- a/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs
+++ b/tests/ConsolePlus.Tests/Unit/ProfileExtensionsTests.cs
@@ -20,6 +20,13 @@ namespace ConsolePlus.Tests.Unit
// de teste sem resetar o cache manualmente — daqui o uso de reflection (mesmo padrão já usado
// para métodos privados estáticos, ex. MaskEditControl.NormalizeStringMask) para zerar o campo
// antes de cada cenário, sem alterar o código de produção.
+ //
+ // Precisa do GlobalStateCollection (DisableParallelization) porque ConsolePlusExtendsTests
+ // também toca esse mesmo cache indiretamente (via inicialização real do singleton
+ // ConsolePlusLibrary.ConsolePlus) — sem isolamento as duas classes competem pelo mesmo campo
+ // static entre threads paralelas do xUnit (achado real, flake intermitente em CI no macOS,
+ // 2026-07-25).
+ [Collection(GlobalStateCollection.Name)]
public class ProfileExtensionsTests : IDisposable
{
// Todas as env vars conhecidas pelos 14 detectores, para isolar cada teste do ambiente real
diff --git a/tests/_driver-src/AnsiScreenInterpreter.cs b/tests/_driver-src/AnsiScreenInterpreter.cs
index 42970f4..c0728d2 100644
--- a/tests/_driver-src/AnsiScreenInterpreter.cs
+++ b/tests/_driver-src/AnsiScreenInterpreter.cs
@@ -62,12 +62,12 @@ private void Feed(char ch)
return;
case State.Esc:
- if (ch == '[') { _state = State.Csi; _private = false; _params.Clear(); return; }
+ if (ch == '[') { _state = State.Csi; _private = false; _ = _params.Clear(); return; }
throw new NotSupportedException($"ESC {ch} is not supported by the test interpreter.");
case State.Csi:
if (ch == '?' && _params.Length == 0) { _private = true; return; }
- if ((ch >= '0' && ch <= '9') || ch == ';') { _params.Append(ch); return; }
+ if ((ch >= '0' && ch <= '9') || ch == ';') { _ = _params.Append(ch); return; }
Dispatch(ch, _params.ToString());
_state = State.Normal;
return;
diff --git a/tests/_driver-src/VirtualScreen.cs b/tests/_driver-src/VirtualScreen.cs
index fa5879d..cf564b4 100644
--- a/tests/_driver-src/VirtualScreen.cs
+++ b/tests/_driver-src/VirtualScreen.cs
@@ -187,7 +187,7 @@ public string TextAt(int row, int col, int len)
var sb = new StringBuilder();
for (int c = col; c < col + len && c < Width; c++)
{
- sb.Append(_cells[row, c].Glyph.ToString());
+ _ = sb.Append(_cells[row, c].Glyph.ToString());
}
return sb.ToString();
}
@@ -201,9 +201,9 @@ public string Snapshot()
var line = new StringBuilder();
for (int c = 0; c < Width; c++)
{
- line.Append(_cells[r, c].Glyph.ToString());
+ _ = line.Append(_cells[r, c].Glyph.ToString());
}
- sb.AppendLine(line.ToString().TrimEnd());
+ _ = sb.AppendLine(line.ToString().TrimEnd());
}
return sb.ToString();
}
diff --git a/tests/_driver-src/VirtualTerminal.cs b/tests/_driver-src/VirtualTerminal.cs
index 5db6950..ba5769c 100644
--- a/tests/_driver-src/VirtualTerminal.cs
+++ b/tests/_driver-src/VirtualTerminal.cs
@@ -117,25 +117,25 @@ public void RaiseResize(int newWidth, int newHeight)
public ConsoleColor ForegroundColor
{
get => Color.ToConsoleColor(_fg);
- set { _fg = value; _writer.ApplyStyle(new Style(_fg, _bg)); }
+ set { _fg = value; _ = _writer.ApplyStyle(new Style(_fg, _bg)); }
}
public ConsoleColor BackgroundColor
{
get => Color.ToConsoleColor(_bg);
- set { _bg = value; _writer.ApplyStyle(new Style(_fg, _bg)); }
+ set { _bg = value; _ = _writer.ApplyStyle(new Style(_fg, _bg)); }
}
public Color ForegroundRgbColor
{
get => _fg;
- set { _fg = value; _writer.ApplyStyle(new Style(_fg, _bg)); }
+ set { _fg = value; _ = _writer.ApplyStyle(new Style(_fg, _bg)); }
}
public Color BackgroundRgbColor
{
get => _bg;
- set { _bg = value; _writer.ApplyStyle(new Style(_fg, _bg)); }
+ set { _bg = value; _ = _writer.ApplyStyle(new Style(_fg, _bg)); }
}
public void ResetColor()
@@ -195,7 +195,7 @@ public void Clear(Color? backgroundcolor = null)
public bool CursorVisible
{
get => _cursorVisible;
- set { if (value) { ShowCursor(); } else { HideCursor(); } }
+ set { if (value) { _ = ShowCursor(); } else { _ = HideCursor(); } }
}
public bool HideCursor() { _writer.Ansi.HideCursor(); _cursorVisible = false; return true; }