mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
tui: harden theme picker cancel and custom theme handling
This commit is contained in:
@@ -2626,6 +2626,7 @@ impl App {
|
||||
self.sync_tui_theme_selection(name);
|
||||
}
|
||||
Err(err) => {
|
||||
self.restore_runtime_theme_from_config();
|
||||
tracing::error!(error = %err, "failed to persist theme selection");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to save theme: {err}"));
|
||||
@@ -2815,6 +2816,27 @@ impl App {
|
||||
self.chat_widget.set_tui_theme(Some(name));
|
||||
}
|
||||
|
||||
fn restore_runtime_theme_from_config(&self) {
|
||||
if let Some(name) = self.config.tui_theme.as_deref()
|
||||
&& let Some(theme) =
|
||||
crate::render::highlight::resolve_theme_by_name(name, Some(&self.config.codex_home))
|
||||
{
|
||||
crate::render::highlight::set_syntax_theme(theme);
|
||||
return;
|
||||
}
|
||||
|
||||
let auto_theme_name = match crate::terminal_palette::default_bg() {
|
||||
Some(bg) if crate::color::is_light(bg) => "catppuccin-latte",
|
||||
_ => "catppuccin-mocha",
|
||||
};
|
||||
if let Some(theme) = crate::render::highlight::resolve_theme_by_name(
|
||||
auto_theme_name,
|
||||
Some(&self.config.codex_home),
|
||||
) {
|
||||
crate::render::highlight::set_syntax_theme(theme);
|
||||
}
|
||||
}
|
||||
|
||||
fn personality_label(personality: Personality) -> &'static str {
|
||||
match personality {
|
||||
Personality::None => "None",
|
||||
|
||||
@@ -407,6 +407,9 @@ impl ListSelectionView {
|
||||
self.complete = true;
|
||||
}
|
||||
} else if selected_item.is_none() {
|
||||
if let Some(cb) = &self.on_cancel {
|
||||
cb(&self.app_event_tx);
|
||||
}
|
||||
self.complete = true;
|
||||
}
|
||||
}
|
||||
@@ -1145,6 +1148,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_with_no_matches_triggers_cancel_callback() {
|
||||
let (tx_raw, mut rx) = unbounded_channel::<AppEvent>();
|
||||
let tx = AppEventSender::new(tx_raw);
|
||||
let mut view = ListSelectionView::new(
|
||||
SelectionViewParams {
|
||||
items: vec![SelectionItem {
|
||||
name: "Read Only".to_string(),
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
}],
|
||||
is_searchable: true,
|
||||
on_cancel: Some(Box::new(|tx: &_| {
|
||||
tx.send(AppEvent::OpenApprovalsPopup);
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
tx,
|
||||
);
|
||||
view.set_search_query("no-matches".to_string());
|
||||
|
||||
view.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
assert!(view.is_complete());
|
||||
match rx.try_recv() {
|
||||
Ok(AppEvent::OpenApprovalsPopup) => {}
|
||||
Ok(other) => panic!("expected OpenApprovalsPopup cancel event, got {other:?}"),
|
||||
Err(err) => panic!("expected cancel callback event, got {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wraps_long_option_without_overflowing_columns() {
|
||||
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
|
||||
|
||||
@@ -232,7 +232,8 @@ pub(crate) fn list_available_themes(codex_home: Option<&Path>) -> Vec<ThemeEntry
|
||||
if path.extension().and_then(|e| e.to_str()) == Some("tmTheme") {
|
||||
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
|
||||
let name = stem.to_string();
|
||||
if !entries.iter().any(|e| e.name == name) {
|
||||
let is_valid_theme = ThemeSet::get_theme(&path).is_ok();
|
||||
if is_valid_theme && !entries.iter().any(|e| e.name == name) {
|
||||
entries.push(ThemeEntry {
|
||||
name,
|
||||
is_custom: true,
|
||||
@@ -886,6 +887,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_available_themes_excludes_invalid_custom_files() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let themes_dir = dir.path().join("themes");
|
||||
std::fs::create_dir(&themes_dir).unwrap();
|
||||
write_minimal_tmtheme(&themes_dir.join("valid-custom.tmTheme"));
|
||||
std::fs::write(themes_dir.join("broken-custom.tmTheme"), "not a plist").unwrap();
|
||||
|
||||
let entries = list_available_themes(Some(dir.path()));
|
||||
|
||||
assert!(
|
||||
entries
|
||||
.iter()
|
||||
.any(|entry| entry.name == "valid-custom" && entry.is_custom),
|
||||
"expected valid custom theme to be listed"
|
||||
);
|
||||
assert!(
|
||||
!entries
|
||||
.iter()
|
||||
.any(|entry| entry.name == "broken-custom" && entry.is_custom),
|
||||
"expected invalid custom theme to be excluded from list"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_theme_name_is_exhaustive() {
|
||||
use two_face::theme::EmbeddedLazyThemeSet;
|
||||
|
||||
Reference in New Issue
Block a user