Skip to content

Add ghostty_config_set_color_scheme C API#39

Open
rodchristiansen wants to merge 1 commit into
manaflow-ai:mainfrom
rodchristiansen:cmux-pr-2710-color-scheme-api
Open

Add ghostty_config_set_color_scheme C API#39
rodchristiansen wants to merge 1 commit into
manaflow-ai:mainfrom
rodchristiansen:cmux-pr-2710-color-scheme-api

Conversation

@rodchristiansen
Copy link
Copy Markdown

@rodchristiansen rodchristiansen commented Apr 14, 2026

Companion to cmux #2710 — terminal surfaces losing theme after config reload.

Summary

Adds a new C API, ghostty_config_set_color_scheme, that lets embedders set the config's conditional theme state before finalization so conditional theme resolution (e.g. theme = light:Foo,dark:Bar) uses the correct scheme during ghostty_config_finalize.

Why

Without this, finalization always uses the ConditionalState default (.light), causing embedded apps (cmux) to load the wrong theme colors until a surface color-scheme change triggers re-derivation. The cmux PR that depends on this API is ghostty-org#2710; merging this first lets the cmux side repoint its ghostty submodule back to manaflow-ai/ghostty#main instead of rod's fork.

Test plan

  • zig build clean
  • Call ghostty_config_set_color_scheme from embedder before ghostty_config_finalize, confirm conditional theme = light:X,dark:Y resolves to the expected scheme
  • cmux PR Plans for bell character ghostty-org/ghostty#2710 green once pointer bumps to this merged SHA

Summary by cubic

Add ghostty_config_set_color_scheme C API to set the config’s color scheme before finalization so conditional themes resolve to the correct light/dark variant. Prevents wrong theme after config reload in embedders like cmux.

  • New Features
    • New API: ghostty_config_set_color_scheme(ghostty_config_t, ghostty_color_scheme_e); call before ghostty_config_finalize to set .light or .dark for conditional theme resolution. Invalid values are ignored with a warning; default remains .light if not set.

Written for commit e0f6eba. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added a new public API function to set color scheme preferences (light or dark) on configuration objects with input validation.

Allows setting the config's conditional theme state before finalization
so that conditional theme resolution (e.g. `theme = light:Foo,dark:Bar`)
uses the correct scheme during ghostty_config_finalize. Without this,
finalization always uses the ConditionalState default (.light), causing
apps to load the wrong theme colors until a surface color scheme change
triggers re-derivation.
Copilot AI review requested due to automatic review settings April 14, 2026 18:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new public C API function to set the color scheme on a configuration object. The function accepts an integer parameter mapping to theme states (0 for light, 1 for dark) and validates input with warning logging for invalid values.

Changes

Cohort / File(s) Summary
API Declaration
include/ghostty.h
Added public API function declaration ghostty_config_set_color_scheme to expose color scheme configuration capability.
Implementation
src/config/CApi.zig
Implemented ghostty_config_set_color_scheme as an exported C function that maps integer scheme values to theme states via conditional state, with validation logging for invalid inputs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Poem

🐰 A color scheme blooms, light and dark entwined,
The API whispers: set_color_scheme, a function kind,
From C to Zig, the theme flows true,
Now configs paint the terminal anew! 🎨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add ghostty_config_set_color_scheme C API' accurately and concisely describes the main change: adding a new public C API function for setting color schemes on configuration objects.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

This PR adds ghostty_config_set_color_scheme to the C API, letting embedders set the conditional theme state on a config object before calling ghostty_config_finalize so that theme = light:Foo,dark:Bar entries resolve to the correct scheme instead of always defaulting to .light. The implementation correctly mutates _conditional_state.theme directly (the changeConditionalState path, which replays all config files, is intentionally not used here since this is a pre-finalization setter).

Confidence Score: 5/5

  • Safe to merge; all findings are minor style and documentation suggestions.
  • The core logic is correct — _conditional_state.theme is set before finalization and the enum values map exactly to conditional.State.Theme. Both findings are P2: one recommends using std.meta.intToEnum for consistency with sibling functions, the other suggests adding a header comment for the before-finalize constraint. Neither blocks correctness.
  • No files require special attention.

Important Files Changed

Filename Overview
src/config/CApi.zig Adds ghostty_config_set_color_scheme that directly mutates _conditional_state.theme before finalization; logic is correct but uses a hardcoded switch instead of std.meta.intToEnum like the rest of the codebase, and no unit test is added.
include/ghostty.h Adds the ghostty_config_set_color_scheme declaration using the pre-existing ghostty_color_scheme_e enum; placement and signature are correct but the declaration lacks a comment explaining the before-finalize requirement.

Sequence Diagram

sequenceDiagram
    participant Embedder as Embedder (e.g. cmux)
    participant CApi as ghostty C API
    participant Config as Config._conditional_state
    participant Finalize as Config.finalize()

    Embedder->>CApi: ghostty_config_new()
    CApi-->>Embedder: "config (theme=.light default)"
    Embedder->>CApi: ghostty_config_load_default_files(config)
    Embedder->>CApi: ghostty_config_set_color_scheme(config, DARK)
    CApi->>Config: "_conditional_state.theme = .dark"
    Embedder->>CApi: ghostty_config_finalize(config)
    CApi->>Finalize: finalize() resolves conditionals using .dark
    Note over Finalize: theme = light:Foo,dark:Bar → resolves to Bar
Loading

Reviews (1): Last reviewed commit: "Add ghostty_config_set_color_scheme C AP..." | Re-trigger Greptile

Comment thread src/config/CApi.zig
Comment on lines +97 to +109
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
self._conditional_state.theme = switch (scheme_raw) {
0 => .light,
1 => .dark,
else => {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
},
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Use intToEnum for consistency with existing color-scheme functions

The sibling functions ghostty_app_set_color_scheme and ghostty_surface_set_color_scheme in embedded.zig both use std.meta.intToEnum(apprt.ColorScheme, scheme_raw) for conversion. Using hardcoded 0/1 here diverges from that pattern and means the mapping stays in sync with conditional.State.Theme's field order only by convention. Prefer the same idiom so any future enum evolution is caught at compile time.

Suggested change
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
self._conditional_state.theme = switch (scheme_raw) {
0 => .light,
1 => .dark,
else => {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
},
};
}
export fn ghostty_config_set_color_scheme(self: *Config, scheme_raw: c_int) void {
const theme = std.meta.intToEnum(conditional.State.Theme, scheme_raw) catch {
log.warn(
"invalid color scheme to ghostty_config_set_color_scheme value={}",
.{scheme_raw},
);
return;
};
self._conditional_state.theme = theme;
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread include/ghostty.h
Comment on lines +1076 to +1077
GHOSTTY_API void ghostty_config_set_color_scheme(ghostty_config_t,
ghostty_color_scheme_e);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing "before finalize" constraint in header comment

The Zig implementation documents that this must be called before ghostty_config_finalize, but the C header has no such note. Embedders working from ghostty.h alone won't see the constraint. If called after finalization the conditional state is updated but the already-resolved theme string is unchanged — a silent no-op that produces wrong colors. A brief comment here would close that gap.

Suggested change
GHOSTTY_API void ghostty_config_set_color_scheme(ghostty_config_t,
ghostty_color_scheme_e);
/* Must be called before ghostty_config_finalize; sets the conditional
theme state so that theme = light:X,dark:Y resolves correctly. */
GHOSTTY_API void ghostty_config_set_color_scheme(ghostty_config_t,
ghostty_color_scheme_e);

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new C embedding API to set a config’s conditional color-scheme state prior to finalization, so conditional theme resolution during ghostty_config_finalize uses the correct light/dark branch (needed by cmux reload flow).

Changes:

  • Add ghostty_config_set_color_scheme exported Zig function to update Config._conditional_state.theme before finalize.
  • Publish the new function in the C header (include/ghostty.h) as part of the embedding API surface.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/config/CApi.zig Implements the new exported config API for setting the conditional theme state used during finalize.
include/ghostty.h Exposes the new config API to C embedders via the public header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config/CApi.zig
const state = &@import("../global.zig").state;
const c = @import("../main_c.zig");

const conditional = @import("conditional.zig");
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const conditional = @import("conditional.zig"); is unused in this file (no references to conditional.*). Zig treats this as an unused import and fails the build. Remove the import, or use it (e.g., for type-safe conversion) so the file compiles.

Suggested change
const conditional = @import("conditional.zig");

Copilot uses AI. Check for mistakes.
Comment thread src/config/CApi.zig
},
};
}

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a new exported C API that affects conditional theme resolution, but there is no unit test covering the new behavior (unlike other functions in this file which have Zig tests). Add a Zig test that sets the scheme via ghostty_config_set_color_scheme before finalize and asserts a conditional theme resolves according to the selected scheme, to prevent regressions.

Suggested change
test "ghostty_config_set_color_scheme resolves conditional theme during finalize" {
const config = ghostty_config_new() orelse return error.OutOfMemory;
defer {
config.deinit();
state.alloc.destroy(config);
}
try edit.set(config, .theme, "light:Foo,dark:Bar");
ghostty_config_set_color_scheme(config, 1);
try config.finalize();
try std.testing.expectEqualStrings("Bar", config.theme.?);
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/config/CApi.zig (1)

93-109: Add regression tests for pre-finalize theme resolution via the new API.

Please add coverage for: (1) setting dark before ghostty_config_finalize influences conditional theme resolution, and (2) invalid scheme values do not mutate an already-set valid theme.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/CApi.zig` around lines 93 - 109, Add unit tests that exercise
ghostty_config_set_color_scheme and ghostty_config_finalize: (1) create a
Config, call ghostty_config_set_color_scheme(self, 1) to set dark before calling
ghostty_config_finalize, then check that conditional theme resolution (e.g.
resolving a value like "light:Foo,dark:Bar" via the same mechanism used in
production tests) yields the dark branch; (2) set a valid scheme first (e.g.
ghostty_config_set_color_scheme(self, 0) or 1), then call
ghostty_config_set_color_scheme(self, 999) with an invalid scheme_raw and assert
Config._conditional_state.theme remains the previously-set valid value and is
not mutated by the invalid call; place these checks in your test suite using the
existing config construction and finalize helpers and assert expected resolved
theme values and retained enum value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/config/CApi.zig`:
- Line 6: Remove the unused import by deleting the line that declares const
conditional = `@import`("conditional.zig"); in CApi.zig; the file only needs the
Config type and references _conditional_state which uses conditional.State
indirectly via Config.zig, so remove the unused conditional symbol to fix the
compile error caused by an unused import.

---

Nitpick comments:
In `@src/config/CApi.zig`:
- Around line 93-109: Add unit tests that exercise
ghostty_config_set_color_scheme and ghostty_config_finalize: (1) create a
Config, call ghostty_config_set_color_scheme(self, 1) to set dark before calling
ghostty_config_finalize, then check that conditional theme resolution (e.g.
resolving a value like "light:Foo,dark:Bar" via the same mechanism used in
production tests) yields the dark branch; (2) set a valid scheme first (e.g.
ghostty_config_set_color_scheme(self, 0) or 1), then call
ghostty_config_set_color_scheme(self, 999) with an invalid scheme_raw and assert
Config._conditional_state.theme remains the previously-set valid value and is
not mutated by the invalid call; place these checks in your test suite using the
existing config construction and finalize helpers and assert expected resolved
theme values and retained enum value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d45ecd5-c9b5-4d94-98dc-06fafc5543bc

📥 Commits

Reviewing files that changed from the base of the PR and between 3b684a0 and e0f6eba.

📒 Files selected for processing (2)
  • include/ghostty.h
  • src/config/CApi.zig

Comment thread src/config/CApi.zig
const state = &@import("../global.zig").state;
const c = @import("../main_c.zig");

const conditional = @import("conditional.zig");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether `conditional` is referenced beyond its declaration.
rg -n '\bconditional\b' src/config/CApi.zig

Repository: manaflow-ai/ghostty

Length of output: 260


🏁 Script executed:

cat -n src/config/CApi.zig | sed -n '85,115p'

Repository: manaflow-ai/ghostty

Length of output: 1216


🏁 Script executed:

# Check full file for any actual usage of conditional (excluding just the import line)
wc -l src/config/CApi.zig && echo "---" && rg -n 'conditional\.' src/config/CApi.zig

Repository: manaflow-ai/ghostty

Length of output: 140


🏁 Script executed:

# Search for _conditional_state and conditional type references in Config
rg -n '_conditional_state|ConditionalState' src/config/CApi.zig -A 2 -B 2

Repository: manaflow-ai/ghostty

Length of output: 323


🏁 Script executed:

# Search for Config struct definition
rg -n 'struct.*Config|pub.*const.*Config' src/config/CApi.zig -A 5 | head -30

Repository: manaflow-ai/ghostty

Length of output: 45


🏁 Script executed:

head -20 src/config/CApi.zig

Repository: manaflow-ai/ghostty

Length of output: 708


🏁 Script executed:

rg -n '_conditional_state' src/config/Config.zig -A 1 -B 1 | head -30

Repository: manaflow-ai/ghostty

Length of output: 1228


Remove unused conditional import (compile blocker).

Line 6 adds const conditional = @import("conditional.zig");, but this symbol is not referenced anywhere in the file. The _conditional_state field uses the conditional.State type, which is already defined in Config.zig; CApi.zig does not need to directly import this module since it only uses the Config type. In Zig, unused imports cause compilation failures.

💡 Proposed fix
-const conditional = `@import`("conditional.zig");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const conditional = @import("conditional.zig");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/CApi.zig` at line 6, Remove the unused import by deleting the line
that declares const conditional = `@import`("conditional.zig"); in CApi.zig; the
file only needs the Config type and references _conditional_state which uses
conditional.State indirectly via Config.zig, so remove the unused conditional
symbol to fix the compile error caused by an unused import.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/config/CApi.zig">

<violation number="1" location="src/config/CApi.zig:6">
P1: Added `conditional` import is unused and can fail Zig compilation due to unused top-level const.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/config/CApi.zig
const state = &@import("../global.zig").state;
const c = @import("../main_c.zig");

const conditional = @import("conditional.zig");
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Added conditional import is unused and can fail Zig compilation due to unused top-level const.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/config/CApi.zig, line 6:

<comment>Added `conditional` import is unused and can fail Zig compilation due to unused top-level const.</comment>

<file context>
@@ -3,6 +3,7 @@ const inputpkg = @import("../input.zig");
 const state = &@import("../global.zig").state;
 const c = @import("../main_c.zig");
 
+const conditional = @import("conditional.zig");
 const Config = @import("Config.zig");
 const c_get = @import("c_get.zig");
</file context>
Fix with Cubic

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