Add configurable color themes#83
Open
mkrueger wants to merge 14 commits into
Open
Conversation
Replaces hard-coded extended-256 Spectre color names (lightyellow3, seagreen2, gold1, orchid, deepskyblue1, violet, plum3, cyan, magenta) with the standard ANSI 16 palette (yellow, green, fuchsia, aqua, purple, blue, etc.). ANSI 0-15 escape sequences resolve through the terminal's configured palette, so the shell's appearance now follows the user's terminal theme (Solarized, Dracula, Campbell, etc.) instead of being stuck at fixed RGB values. Key gotcha: Spectre's 'cyan' and 'magenta' map to fixed 256-color indices and bypass terminal theming. The themable equivalents are 'aqua' and 'fuchsia', and we use both names today, so this is not a no-op rename. All color choices now flow through Theme.cs constants (Theme.X- ColorName) and helpers (Theme.FormatX). Inline [color]...[/] markup literals were swept out of HelpCommand, ConnectCommand, DirCommand, ListCommand, SettingsCommand, QueryCommand, BucketCommand, and ShellInterpreter so the palette has a single source of truth. JsonOutputHighlighter tests now derive expected colors from Theme constants instead of literal color names so future palette tweaks do not churn the assertions. Also removed two dead [magenta]...[/] wrappers around Theme.Format- Keyword in HelpCommand: the inner style already wins inside Spectre so the outer color was never visible. Verified: dotnet build clean, 877 tests pass / 0 fail / 74 skipped (integration tests requiring the emulator).
Follow-up to 7587eb4. The earlier sweep regex matched [color] and [color ...] forms but missed [bold cyan] in HelpCommand category and statement-help headers. The dark teal Spectre 'cyan' (256-color #51) bypasses terminal theming and is unreadable on light backgrounds. Replaced both with Theme.ConnectedPromptColorName ('aqua') so the headers now follow the terminal palette. Verified the rest of the codebase by sweeping every named non-ANSI-16 Spectre color (cyan/magenta/violet/orchid/gold1/plum3/deepskyblue1/ seagreen2/lightyellow3/etc.); only doc comments remain.
The screenshots showed two readability problems on light terminal backgrounds: - Category headers (Connection, Data Operations, ...) used 'aqua' (ANSI 14 / bright cyan), which Windows Terminal Campbell Light renders as a borderline-dark teal. - Command descriptions and option/argument help text used 'silver' and 'white' (ANSI 7 and 15), both of which render as light gray on light backgrounds and become invisible against white. Spectre has no [default] color marker, so the cleanest fix is to emit no foreground color at all and let the terminal's configured default foreground handle contrast in both light and dark themes. Added three intent-named Theme helpers: - FormatHelpHeader -> [bold]X[/] (terminal foreground, bold) - FormatHelpName -> [bold]X[/] (parameter/option syntax) - FormatHelpDescription -> Markup.Escape(X) (no styling) Swept all of the affected HelpCommand call sites to use them: category headers, command/statement descriptions, argument and option help tables, example ordinals, statement help table, and the section rule title. Accent uses (the >> example bullet, syntax highlighting in the code block) keep their colors on purpose - they're single characters or decorative tokens where the bright color is intentional. Verified: build clean, 51 targeted tests pass.
The 'Available Commands' and 'Control Flow Statements' Panel titles live in en.ftl as [bold white]...[/]. White on a light terminal background is invisible (matches the screenshot showing an empty bordered box). Switched both to [bold] so the title falls through to the terminal's default foreground, consistent with the help headers fixed in e37b59b.
Localization data should describe content, not styling. Stripped five entries: - command-query-request_charge: dropped [white]...[/] wrapper (caller already uses MarkupLine, so the value just renders in the terminal default fg) - mcp-error-creating-server / mcp-error-server-failed-start: dropped [red]...[/] wrappers (callers use AnsiConsole.WriteLine not MarkupLine, so the markup was being printed literally as text - pre-existing bug, now no-op) - help-available-commands-styled / help-control-flow-statements-styled: removed entirely; the two Panel call sites in HelpCommand now wrap the bare keys through Theme.FormatHelpHeader, matching the rest of the help styling Normalised the colon on help-control-flow-statements: stripped from the FTL value, appended at the plain-text call site so the styled panel title doesn't end with a stray ':'. Verified: build clean, help and localization-audit tests pass.
Introduces a runtime-swappable ThemeOptions record consumed by every
Theme.* helper. Defaults are unchanged, so existing call sites keep
working via the same XColorName accessors (now properties that read
from Theme.Current).
Three user-visible surfaces:
1. Startup CLI flag --theme=<name>, with fallback to the
COSMOSDB_SHELL_THEME environment variable. Unknown names emit a
warning and fall back to the default profile.
2. Built-in profiles defined in ThemeProfiles:
- default / dark: today's palette (ANSI 16, dark-bg friendly)
- light: darker hues for brackets/literals/prompt so they stay
readable on white backgrounds
- monochrome: no foreground colors at all, only bold/dim/underline
3. Interactive 'theme' command with sub-actions:
- theme -> show active profile name
- theme list -> list available profiles
- theme show [n] -> render a sample of every role using profile
n (or active if omitted), without changing the active theme
- theme use <n> -> swap active profile for the rest of the session
Localisation:
- help-Theme, command-theme-* keys for the new command
- warning-unknown-theme for the CLI fallback path
All values are color-free, matching the FTL hygiene rule established
earlier on this branch.
Tests (ThemeProfileTests):
- TryGet matrix (known/unknown/blank)
- Apply round-trip and helper-output coupling
- Monochrome emits no foreground colors
- Lint guard: every slot in every built-in profile is either empty,
an ANSI 16 color name, or a Spectre style modifier; catches any
accidental reintroduction of fixed-256 names like cyan/violet/
plum3 going forward
- Localization key audit for the new keys
All 877 existing tests still pass (verified with HelpCommand, Highlighter,
JsonOutput, LocalizationKeyAudit suites). Smoke-tested 'theme list',
'--theme=monochrome -c theme', and the unknown-theme warning path.
Out of scope (Phase B follow-ups): file-based themes, --theme-color
per-role overrides at the CLI, runtime persistence ('theme save'),
true-color hex support, profile-customization.
The Theme.CommandColor accessor returned the open-tag fragment '[yellow]' (or empty string for monochrome), and two HelpCommand call sites paired it with a hardcoded '[/]'. In monochrome that produced 'connect[/]' -- an unbalanced markup string that crashed Spectre with 'Encountered closing tag when none was expected near position 9'. Fix: use the existing Theme.FormatCommand helper, which always emits balanced markup including the empty-style case. Removed the now-unused open-tag accessor and its helper so the same pattern can't be reintroduced. Added a regression test in ThemeProfileTests that runs every Theme.Format* helper for every built-in profile through Spectre's Markup parser. The old code would have failed this test for the monochrome profile. Verified: monochrome 'help' renders cleanly; 24 theme/help tests pass.
Users can drop *.toml files in ~/.cosmosdbshell/themes/ to define color
themes that appear alongside the built-in default/light/dark/monochrome
profiles. File schema is small and uses the existing slot vocabulary:
name = "solarized-light"
description = "Solarized-style light palette"
extends = "default"
[colors]
literal = "purple"
bracket_cycle = ["purple", "maroon", "navy"]
[styles]
help_header = "bold underline"
New code:
- ThemeOptions / Theme.Apply already existed; this change adds
ThemeFile (TOML parse/save with extends), ThemePalette (allowed
values), and ThemeRegistry (merged built-in + file lookup with
cycle / unknown-extends detection).
- Program.cs scans the user themes directory at startup so --theme=
<name> and COSMOSDB_SHELL_THEME hit the merged set.
- ThemeCommand gains three new sub-actions:
theme load <path> -> load and switch to a single file
theme save <name> -> write the active theme to ~/.cosmosdbshell
/themes/<name>.toml (or a custom path,
with --force to overwrite)
theme reload -> rescan the user themes directory
'theme list' now shows source (built-in vs file) and the file path.
Validation:
- All values must be ANSI 16 color names or Spectre style modifiers
(centralised in ThemePalette; the existing built-in profile lint
guard now reuses the same set).
- extends chain depth capped at 8; cycles and unknown bases are
rejected as ThemeLoadException with localized messages.
- File themes shadowing built-ins emit a warning, not an error.
- Save writes only slots that differ from the default profile, so
user files stay minimal; bracket_cycle is folded into the single
[colors] section so the TOML is valid.
Tests:
- ThemeFileTests (10): parse/load/save round trip, defaults,
unknown-key warnings, invalid color rejection, unknown/self
extends, empty bracket cycle, multi-token style.
- ThemeRegistryTests (6): built-ins seeded, file load + warnings,
cycle detection, ResetToBuiltIns, shadowing warning.
All 41 theme-related tests pass; existing help and localization
audit tests still pass.
Smoke-tested end to end:
- File in user dir is auto-discovered: 'theme list' shows
'ocean (~/.cosmosdbshell/themes/ocean.toml)'.
- --theme=ocean activates it at startup.
- 'theme save my-light' from --theme=light writes a clean file with
only the differing slots and a single [colors] section.
Package: adds Tomlyn 0.19.0 (MIT).
Three fixes from the user's transcript:
1. 'theme save' used to write a minimal file with extends='default' and
silently leave the registry stale. Behaviour was confusing: the new
theme didn't appear in 'theme list' until the next session. Now
save:
- Writes every slot explicitly with no extends line, so files are
fully self-contained and don't depend on any base profile being
present at load time.
- Prints a hint pointing at 'theme reload' / 'theme load <name>'
so users know how to pick up the change.
No auto-registration: the user explicitly said they don't want
that, because it wouldn't reflect files copied/removed externally.
2. 'theme load mylight' previously failed with 'Theme file not found:
mylight' because the argument was treated as a literal path. The
resolver now: if the argument has no path separator and isn't an
existing file, treat it as a name in the user themes directory and
look up '~/.cosmosdbshell/themes/<name>.toml' (with or without the
.toml suffix).
3. ThemeFile.Save signature simplified: dropped the baseline and
extends parameters since the new behaviour writes everything.
Removed the no-longer-needed AppendSection and RenderBracketCycle-
Line helpers. Tests updated:
- SaveAndLoad_RoundTripsModifiedSlots now asserts every slot is
written and 'extends' is absent.
- New Save_WithoutExtends_ProducesSelfContainedFile proves a saved
file loads correctly even when no base profile is available.
Localisation: new key command-theme-save-hint-reload.
Build clean, 42 theme/help/localization tests pass. Smoke-tested
end-to-end: save writes the file but list omits it (no auto-reg);
load-by-name resolves to the user dir; saved file is fully populated
with no extends line.
There was a problem hiding this comment.
Pull request overview
This PR introduces shell-wide color theming for CosmosDBShell, including built-in ANSI-16-only theme profiles, TOML-based custom themes (with extends), startup selection, and a new theme command to inspect/switch/load/validate/save/reload themes. It also updates existing command output to use theme-aware formatting helpers, adds localized strings, and includes focused test coverage for the new theming surface.
Changes:
- Add a theming core (theme options/profiles, palette, registry, TOML load/save/validation) and apply a selected theme at startup.
- Add a
themeshell command for listing/showing/using/loading/validating/saving/reloading themes. - Replace ad-hoc Spectre markup across commands/help/errors with theme-aware
Theme.Format*helpers; update docs/localization and add tests.
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents --theme startup flag and custom theme workflow. |
| docs/commands.md | Documents the new theme command usage and behavior. |
| Directory.Packages.props | Adds central package version for Tomlyn (TOML parsing). |
| CosmosDBShell/CosmosDBShell.csproj | Adds Tomlyn package reference. |
| CosmosDBShell/Program.cs | Applies selected theme at startup; loads user theme directory into registry. |
| CosmosDBShell/lang/en.ftl | Adds theme-related localized strings; adjusts some existing strings for theme formatting. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Util/LocalizableSentenceBuilder.cs | Adds localized help text for the new --theme option. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.KeyBindings/ReverseHistorySearch.cs | Uses themed search-match style for reverse history highlighting. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/Theme.cs | Implements theme state (Current) plus formatting helpers and compatibility accessors. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeOptions.cs | Defines the full set of theme slots used across the shell. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeProfiles.cs | Adds built-in profiles (default/light/dark/monochrome) and lookup. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemePalette.cs | Centralizes allowed ANSI-16 colors/modifiers and suggestion logic. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeFile.cs | Implements TOML parse/validate/save with warnings/errors aggregation. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeFileResult.cs | Represents successful theme-file load/parse results (including warnings). |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeLoadException.cs | Defines an exception type for theme file load failures. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeRegistry.cs | Maintains merged built-in + file themes; supports directory scans and ad-hoc file loads. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeRegistration.cs | Represents a theme entry (source, path, description, extends). |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ThemeSource.cs | Adds an enum for theme origin (built-in vs file). |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs | Routes error/warning output through theme formatting helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ThemeCommand.cs | Adds the new theme command implementation and JSON outputs. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/SettingsCommand.cs | Updates output styling to use theme helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/QueryCommand.cs | Updates table metric output styling to use theme helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ListCommand.cs | Updates listing output styling to use theme helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/HelpCommand.cs | Updates help styling (headers/names/descriptions) to be theme-aware. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/FtabCommand.cs | Updates table header styling to use theme helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/DirCommand.cs | Updates directory listing styling to use theme helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/ConnectCommand.cs | Updates connection info output styling to use theme helpers. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Commands/BucketCommand.cs | Updates throughput bucket output styling to use theme helpers. |
| CosmosDBShell.Tests/UtilTest/JsonOutputHighlighterTests.cs | Aligns JSON highlighting assertions with theme-based color slots. |
| CosmosDBShell.Tests/Shell/ThemeRegistryTests.cs | Adds tests for registry load/reset behavior and warnings. |
| CosmosDBShell.Tests/Shell/ThemeProfileTests.cs | Adds tests to lint built-in profiles and validate helper markup across profiles. |
| CosmosDBShell.Tests/Shell/ThemeFileTests.cs | Adds tests for TOML parsing/validation/suggestions and save/load round-trips. |
| CosmosDBShell.Tests/Shell/HotkeyCommandTests.cs | Adds regression test for monochrome reverse-search highlighting. |
| CosmosDBShell.Tests/CommandTests/ThemeCommandTests.cs | Adds tests for theme validate behavior (file/dir/strict). |
- Make ThemeProfiles.Dark a distinct instance from Default so ResolveActiveName's reference-equality lookup correctly maps the active theme back to "dark" (and "default" stays "default"). - Drop Markup.Escape on MCP error messages; the en.ftl strings no longer contain markup and the callers use AnsiConsole.WriteLine, which would otherwise emit literal [[ / ]] sequences. - Escape user-controlled values (theme names, file paths, action, directory) in ThemeCommand markup output so a theme name or path containing [ / ] cannot break Spectre markup parsing or inject styling. - Include current and the use/set alias in the unknown-action message so it reflects the real command surface. - Add regression test pinning that ThemeProfiles.Default and Dark are distinct instances.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds shell-wide color theming with built-in profiles, TOML-based custom themes, and a
themecommand for inspection, switching, validation, save, and reload.Highlights
default,light,dark,monochrome) using only ANSI 16 colors so output follows the terminal palette (Solarized, Dracula, Campbell, ...).~/.cosmosdbshell/themes/*.tomlwithextendsinheritance and a self-containedtheme saveround-trip.themecommand:current,list,show [name],use [name],load [path],validate [path|dir],save [name],reload.--strictto promote warnings to errors.Theme.Format*helpers so monochrome and light themes work end-to-end.--theme=<name>andCOSMOSDB_SHELL_THEMEenv var.lang/en.ftl.docs/commands.mddocuments thethemecommand.Tests
Added focused tests: theme profile lint, file parse/save round-trip, registry behavior, command surfaces (validate / use / load / save), monochrome reverse-search highlighting regression, and localization key audit.