Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions code-rs/config/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::collections::HashMap;
use std::fmt;

use schemars::JsonSchema;
use serde::Deserializer;
use serde::Deserialize;
use serde::Serialize;

Expand Down Expand Up @@ -664,7 +665,7 @@ pub struct Tui {
///
/// Using alternate screen provides a cleaner fullscreen experience but prevents
/// scrollback in terminal multiplexers like Zellij that follow the xterm spec.
#[serde(default)]
#[serde(default, deserialize_with = "deserialize_alt_screen_mode")]
pub alternate_screen: AltScreenMode,

/// Ordered list of status line item identifiers.
Expand Down Expand Up @@ -692,7 +693,7 @@ pub struct Tui {
///
/// When set, overrides automatic light/dark theme detection.
/// Use `/theme` in the TUI or see `$CODEX_HOME/themes` for custom themes.
#[serde(default)]
#[serde(default, deserialize_with = "deserialize_tui_theme")]
pub theme: Option<String>,

/// Preferred layout for resume/fork session picker results.
Expand Down Expand Up @@ -740,6 +741,41 @@ pub struct ExternalConfigMigrationPrompts {
pub project_last_prompted_at: BTreeMap<String, i64>,
}

fn deserialize_alt_screen_mode<'de, D>(deserializer: D) -> Result<AltScreenMode, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum AltScreenCompat {
Mode(AltScreenMode),
LegacyBool(bool),
}

match AltScreenCompat::deserialize(deserializer)? {
AltScreenCompat::Mode(mode) => Ok(mode),
AltScreenCompat::LegacyBool(true) => Ok(AltScreenMode::Always),
AltScreenCompat::LegacyBool(false) => Ok(AltScreenMode::Never),
}
}

fn deserialize_tui_theme<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum TuiThemeCompat {
Name(String),
LegacyTable { name: String },
}

Ok(Option::<TuiThemeCompat>::deserialize(deserializer)?.map(|theme| match theme {
TuiThemeCompat::Name(name) => name,
TuiThemeCompat::LegacyTable { name } => name,
}))
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct Notice {
Expand Down
62 changes: 62 additions & 0 deletions code-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,53 @@ terminal_resize_reflow_max_rows = 9000
);
}

#[test]
fn config_toml_deserializes_legacy_tui_alternate_screen_bool() {
let enabled_toml = r#"
[tui]
alternate_screen = true
"#;
let enabled_cfg: ConfigToml = toml::from_str(enabled_toml)
.expect("TOML deserialization should accept legacy alternate_screen bool");
assert_eq!(
enabled_cfg
.tui
.expect("tui config should deserialize")
.alternate_screen,
AltScreenMode::Always
);

let disabled_toml = r#"
[tui]
alternate_screen = false
"#;
let disabled_cfg: ConfigToml = toml::from_str(disabled_toml)
.expect("TOML deserialization should accept legacy alternate_screen bool");
assert_eq!(
disabled_cfg
.tui
.expect("tui config should deserialize")
.alternate_screen,
AltScreenMode::Never
);
}

#[test]
fn config_toml_deserializes_tui_alternate_screen_string_mode() {
let toml = r#"
[tui]
alternate_screen = "always"
"#;
let cfg: ConfigToml = toml::from_str(toml)
.expect("TOML deserialization should accept alternate_screen string mode");
assert_eq!(
cfg.tui
.expect("tui config should deserialize")
.alternate_screen,
AltScreenMode::Always
);
}

#[tokio::test]
async fn runtime_config_defaults_model_availability_nux() {
let cfg = Config::load_from_base_config_with_overrides(
Expand Down Expand Up @@ -2155,6 +2202,21 @@ theme = "dracula"
);
}

#[test]
fn tui_theme_deserializes_legacy_table_from_toml() {
let cfg = r#"
[tui.theme]
name = "dark-oled-black-pro"
is_dark = true
"#;
let parsed = toml::from_str::<ConfigToml>(cfg)
.expect("TOML deserialization should accept legacy TUI theme table");
assert_eq!(
parsed.tui.as_ref().and_then(|t| t.theme.as_deref()),
Some("dark-oled-black-pro"),
);
}

#[test]
fn tui_theme_defaults_to_none() {
let cfg = r#"
Expand Down
3 changes: 3 additions & 0 deletions code-rs/features/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ impl Features {
"image_detail_original" => {
continue;
}
"skills" => {
continue;
}
"use_legacy_landlock" => {
self.record_legacy_usage_force(
"features.use_legacy_landlock",
Expand Down
8 changes: 8 additions & 0 deletions code-rs/features/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ fn image_detail_original_is_removed_and_disabled_by_default() {
assert_eq!(Feature::ImageDetailOriginal.default_enabled(), false);
}

#[test]
fn skills_feature_key_is_accepted_as_removed_noop() {
let mut features = Features::with_defaults();
features.apply_map(&BTreeMap::from([("skills".to_string(), true)]));

assert_eq!(features.enabled(Feature::ShellTool), true);
}

#[test]
fn code_mode_only_requires_code_mode() {
let mut features = Features::with_defaults();
Expand Down