Skip to content

Add Terminal theme that inherits the terminal's ANSI colour scheme#176

Merged
BrettKinny merged 2 commits into
mainfrom
claude/terminal-ansi-theme-y7bggl
Jun 11, 2026
Merged

Add Terminal theme that inherits the terminal's ANSI colour scheme#176
BrettKinny merged 2 commits into
mainfrom
claude/terminal-ansi-theme-y7bggl

Conversation

@BrettKinny

Copy link
Copy Markdown
Collaborator

Adds a third built-in theme that renders using the terminal's own
16-color ANSI palette instead of fixed 24-bit RGB values:

  • TerminalTheme defines all colors with the exact ColorName16 values so
    they map onto ANSI palette indices without approximation
  • AppTheme gains a UseTerminalColors flag; ThemeManager.SetTheme toggles
    Application.Force16Colors (and the active driver) accordingly, so the
    driver emits standard SGR 30-37/90-97 codes that the terminal renders
    with its configured colour scheme
  • View menu theme toggle now cycles Dark -> Light -> Terminal

https://claude.ai/code/session_01PH2h6mTwUqKqdx5dZEceCi

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code Review

Overview

This PR adds a TerminalTheme that restricts color output to the 16 named ANSI colors, letting the terminal apply its own configured palette. AppTheme gains a UseTerminalColors flag, ThemeManager propagates it to Application.Force16Colors, the theme toggle cycles generically through N themes instead of hardcoding 2, and tests/docs are updated. The approach is sound.


Potential Issues

1. Driver propagation is coupled to an implementation detail

The comment in ThemeManager.cs acknowledges this workaround for the v2 facade driver caching its own flag. If Terminal.Gui v2 fixes this in a future release, the propagation will silently double-set the flag (harmless) or — if the property is removed — break compilation. Worth adding a version-pinned comment so it is clear when this can be removed.

2. EnableGlow => true in a 16-color context

TerminalTheme sets EnableGlow => true. If glow is rendered via alpha-blended halos or brightness gradients, these degrade silently to nearest-ANSI neighbours in 16-color mode and could look muddy. Has this been tested visually? If not, defaulting to false is the safer choice for a first pass.

3. Test covers Application.Force16Colors but not the driver path

ThemeManager_SetTerminalTheme_TogglesForce16Colors only asserts Application.Force16Colors. Because Application.Driver is null in unit tests, the driver.Force16Colors = ... branch is never exercised. This is expected for a unit test, but worth a brief comment so it is not later mistaken for a complete check.

4. Global static mutation and parallel tests

The new test sets Application.Force16Colors through ThemeManager (global mutable state). The finally block restores the theme correctly, but if xUnit runs ThemeManagerTests.cs in parallel with other classes that read Application.Force16Colors, results could be non-deterministic. The existing theme tests have the same exposure, but it is worth noting here because this is the first test to explicitly assert on Force16Colors.


Minor / Nits

Misleading comment in TerminalTheme.cs: The comment says "All colors use the exact RGB values Terminal.Gui maps to the 16 ANSI color indices" — but the colors are constructed with ColorName16 enum values directly, not RGB values. Suggested: "All colors are constructed with ColorName16 enum values, so GetClosestNamedColor16 resolves them to the exact ANSI index (no approximation)."

Assert.All: TerminalTheme_AllColorsMapExactlyToAnsiColors could use Assert.All instead of foreach for cleaner xUnit failure messages identifying the specific color that failed.

ScopeView signal colors: The five oscilloscope signal colors (Green, Cyan, Yellow, Magenta, White) are hardcoded in ScopeView outside the theme system. They will be quantised to nearest ANSI equivalents automatically — probably fine, but worth a quick manual check of scope rendering under the Terminal theme to confirm traces remain readable.


What Is Working Well

  • UseTerminalColors keeps the abstraction clean — non-terminal themes are unaffected, default to false, require no code changes.
  • Making ToggleTheme and GetThemeToggleTitle generic over AvailableThemes.Count avoids a future regression when a fourth theme is added.
  • Test coverage is thorough: round-trip ANSI check, name/description/flag assertions, updated theory cases for all existing parametric tests.
  • CLAUDE.md and README are both updated, keeping docs in sync.

Summary

Solid, well-structured addition. The two items most worth addressing before merge are the EnableGlow visual risk in 16-color mode and the misleading "RGB values" comment. Everything else is minor polish.

claude added 2 commits June 11, 2026 02:59
Adds a third built-in theme that renders using the terminal's own
16-color ANSI palette instead of fixed 24-bit RGB values:

- TerminalTheme defines all colors with the exact ColorName16 values so
  they map onto ANSI palette indices without approximation
- AppTheme gains a UseTerminalColors flag; ThemeManager.SetTheme toggles
  Application.Force16Colors (and the active driver) accordingly, so the
  driver emits standard SGR 30-37/90-97 codes that the terminal renders
  with its configured colour scheme
- View menu theme toggle now cycles Dark -> Light -> Terminal

https://claude.ai/code/session_01PH2h6mTwUqKqdx5dZEceCi
@BrettKinny BrettKinny force-pushed the claude/terminal-ansi-theme-y7bggl branch from 65e7f0b to 2df3088 Compare June 11, 2026 03:01
@BrettKinny BrettKinny merged commit 8e0533c into main Jun 11, 2026
2 checks passed
@claude

claude Bot commented Jun 11, 2026

Copy link
Copy Markdown

Code Review: Add Terminal theme that inherits the terminal's ANSI colour scheme

Note: This PR is already merged. Review provided for completeness and historical reference.

Overview

This PR adds a third built-in theme (TerminalTheme) that renders using the terminal's own 16-color ANSI palette instead of fixed 24-bit RGB values. The implementation is clean and well-scoped: a new theme class, a virtual flag on the base class, propagation logic in ThemeManager, and updated cycling in MainWindow. The approach is architecturally sound and the test coverage is notably thorough.


Strengths

  • Backward-compatible design. UseTerminalColors defaults to false on the base class, so existing themes require no changes.
  • Proper dual-flag propagation. ApplyTerminalColorMode correctly sets both Application.Force16Colors and driver.Force16Colors to handle the v2 facade's cached flag — the comment explaining why is appropriately placed.
  • MainWindow scaling. GetThemeToggleTitle and ToggleTheme now use AvailableThemes.Count instead of the hardcoded % 2 modulus, so adding future themes requires no changes to MainWindow.
  • Round-trip color test. TerminalTheme_AllColorsMapExactlyToAnsiColors is an excellent property-based sanity check — it enforces the contract that no color will be approximated by the driver.
  • Full parameterized test extension. All 10 existing parameterized theme test theories are correctly extended to include TerminalTheme.

Issues and Suggestions

Minor: Static state in Force16Colors test risks flakiness under parallel execution

The try/finally cleanup in ThemeManager_SetTerminalTheme_TogglesForce16Colors is appropriate, but because Application.Force16Colors is a global static, a concurrently executing test that reads or writes it could see an inconsistent state. If the test suite ever moves to parallel class execution, this could become flaky. Low-risk given the current test configuration, but worth noting.

Minor: ColorScheme attributes not covered by the round-trip test

TerminalTheme_AllColorsMapExactlyToAnsiColors validates raw Color properties but not the Attribute values embedded in MainColorScheme and MenuColorScheme. Since those schemes hardcode new Color(ColorName16.DarkGray) for focus backgrounds, the invariant holds in practice — but a test that also exercises the scheme attributes would close the gap completely.

Observation: Scope view signal colors

ScopeView uses fixed signal colors (Green, Cyan, Yellow, Magenta, White) defined independently of the theme system. When Terminal theme is active, Force16Colors is true, so those colors will be emitted as 16-color ANSI codes — the desirable behavior. Worth confirming those colors are defined using ColorName16 values rather than 24-bit RGB; if they use RGB, they would be approximated by the driver despite the theme's intent.

Nit: Test cleanup uses a different API than the rest of the test

The finally block calls ThemeManager.SetTheme(new DarkTheme()) while the rest of the test uses SetTheme(string). Both are correct; using SetTheme("Dark") would be more consistent with the surrounding code.


Summary

Area Assessment
Code correctness Correct — dual-flag propagation handles the v2 driver edge case
Project conventions Follows naming, minimal comments, no unnecessary abstractions
Performance No impact — flag toggle is O(1) and only happens on theme switch
Test coverage Excellent — round-trip invariant test is particularly strong
Security No concerns

Clean, well-tested feature addition. The one follow-up worth checking: whether ScopeView signal colors are expressed as ColorName16 values so they render correctly (without approximation) when Terminal theme is active.

BrettKinny added a commit that referenced this pull request Jun 11, 2026
…yer 1) (#165)

* Upgrade Terminal.Gui 2.0.0 -> 2.4.5 (full UI-layer migration)

The component-test APIs (Application.Create, InjectKey/InjectSequence,
VirtualTimeProvider) first appear in Terminal.Gui 2.1.0, which also landed a
breaking API redesign. This ports the whole UI layer to 2.4.5:

- Namespace reorg: add GlobalUsings for Terminal.Gui.{App,ViewBase,Views,
  Drawing,Input,Drivers,Text,Configuration}; alias Attribute -> Drawing.Attribute.
- ColorScheme -> Scheme; view.ColorScheme = x -> view.SetScheme(x); add a
  WithScheme() fluent helper for initializer-style scheme assignment.
- Toplevel -> Window; RadioGroup -> OptionSelector (Labels/Value/ValueChanged).
- TableView: SelectedRow -> Value?.SelectedCell.Y; SelectedCellChanged ->
  ValueChanged<TableSelection>; MouseClick -> MouseEvent(Mouse).
- TreeView ObjectActivated -> Activated (read SelectedObject).
- ListView OpenSelectedItem -> Accepting; drop TopItem (SelectedItem autoscrolls).
- MenuItem shortcutKey: named arg -> positional Key.
- Custom drawing: Driver!.X(...) -> view-level SetAttribute/AddRune/AddStr/Move
  in OnDrawingContent(DrawContext).
- Application.Top -> TopRunnable/TopRunnableView; SizeChanging -> SubViewLayout;
  Colors.ColorSchemes["Menu"] -> SchemeManager.AddScheme; MessageBox.* now take
  Application.Instance; ShadowStyle.None -> ShadowStyles.None; Subviews -> SubViews;
  TextField.CursorPosition -> MoveEnd().

Known regression (flagged for review): Terminal.Gui 2.4 adornments have no
independent Scheme, so per-border colouring (grey borders, focus-highlight title)
now inherits the view scheme; focus highlight reapplied via FrameView scheme.

Builds clean; all 613 existing tests pass; published --help smoke exits 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add layer-1 in-process TUI component tests

Constructs the real Terminal.Gui views/dialogs and asserts on observable
component behaviour:
- MonitoredVariablesView: AddVariable/RemoveVariable, scope-selection bookkeeping,
  per-client-handle idempotency.
- ConnectDialog: endpoint protocol-prefix handling, publishing interval, and the
  authentication selector (migrated RadioGroup -> OptionSelector).
- Theme/Scheme: DarkTheme/LightTheme schemes and ThemeStyler.ApplyTo guard the
  ColorScheme -> Scheme migration.

Tests run in a non-parallel xUnit collection because Terminal.Gui's Application is
global state. Picked up automatically by `dotnet test` / CI.

Rendered cell-buffer assertions are intentionally deferred to the black-box PTY
suite: Terminal.Gui 2.4.5 stable exposes no public headless driver (Application.Create
leaves Driver null; DriverAssert is develop-only). Documented in docs/TESTING.md.

627/627 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix behavioral regressions found by migration review

Adversarial review of the Terminal.Gui 2.0->2.4 migration confirmed three
event-routing / API-semantics regressions (TG 2.4 moved selection/activation from
overridden handlers into the command pipeline, which now runs AFTER the raw
MouseEvent/Accepting events):

- MonitoredVariablesView.HandleMouseClick: dropped `e.Handled = true` on Sel-column
  clicks. Marking it handled now pre-empts TableView's LeftButtonClicked ->
  Command.Activate -> SetSelection, so a Sel click toggled scope but no longer
  highlighted the row or raised SelectedVariableChanged. Unhandled restores both.
- SaveRecordingDialog.OnFileListOpenSelected: set `e.Handled = true`. An unhandled
  Accepting bubbles Accept to the default Save button, so navigating a folder or
  picking a file would save+close the dialog mid-browse.
- SaveRecordingDialog.NavigateToSelected: null-safe guard. ListView.SelectedItem is
  now int? (null = none); the OR-form guard didn't catch null and dereferenced
  null.Value (InvalidOperationException at a CSV-less filesystem root).
- LogView: restore log auto-scroll via EnsureSelectedItemVisible() (TG 2.4 removed
  ListView.TopItem); without it the log stopped following new entries.

627/627 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Migrate Terminal theme and tests added on main to Terminal.Gui 2.4.5

Rebasing onto main brought in the Terminal theme (#176) and its tests,
which were written against Terminal.Gui 2.0:

- TerminalTheme: ColorScheme -> Scheme; drop the local Attribute alias
  now provided by GlobalUsings.
- ThemeManager: Application.Force16Colors no longer exists as a static;
  set the flag on Application.Driver only.
- ThemeManagerTests: assert UseTerminalColors on the theme since the
  driver (which now owns Force16Colors) is null in headless tests.
- ThemeSchemeTests: ThemeStyler.ApplyTo was dropped in favour of main's
  leaner surface (#177); test ApplyToFrame instead.

* Address review feedback: null-safety, readability, and test coverage

- ScopeDialog: replace GetScheme()! with an explicit fallback to the
  theme's main scheme, removing the NRE risk reviewers flagged.
- ConnectDialog: normalize an empty password box to null so Password
  honours its nullable contract; downstream sends the same bytes.
- Expand the collapsed single-line Scheme initializers in HelpDialog,
  WriteValueDialog and MonitoredVariablesView to the multi-line style
  used elsewhere.
- AppTheme: mark the now-unapplied HighlightTitleBorderColorScheme with
  a TODO tied to the planned VisualRoles work.
- AddressSpaceView: document why reading SelectedObject in Activated is
  race-free.
- Tests: cover TerminalTheme in ThemeSchemeTests, add a WithScheme unit
  test, assert Password stays null without input, and dispose views in
  TUI tests to stop ThemeChanged handler leaks across the collection.
- docs/TESTING.md: note the E2E project ships in the follow-up PR.

* Address follow-up review: obsolete dead scheme, typed-password test, polish

- AppTheme: mark HighlightTitleBorderColorScheme [Obsolete] so future
  callers can't silently rely on a scheme that is never applied.
- ConnectDialogTests: cover the non-null path of the Password getter by
  typing into the dialog's Secret TextField.
- MonitoredVariablesView: note that Mouse.Position is Point? in
  Terminal.Gui 2.4 so the null pattern isn't stripped as redundant.
- NodeDetailsView: use WithScheme for the copy button like every other
  initializer in the migration.
- ScopeView: drop a stray blank line left by the Driver-null-check
  removal.

* Add TUI screenshots verifying the Terminal.Gui 2.4.5 migration

Captured against the in-repo OPC UA test server (tmux + ANSI render):
main window in all three themes, the migrated Connect dialog
(OptionSelector), and the braille scope with four live signals.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BrettKinny BrettKinny deleted the claude/terminal-ansi-theme-y7bggl branch July 11, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants