diff --git a/src/cli.rs b/src/cli.rs index 93bb72c..d2177e3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -50,7 +50,7 @@ pub struct Cli { #[arg(long, global = true, action = ArgAction::SetTrue)] pub no_color: bool, - /// Color theme for the output: default, ocean, sunset, forest, dracula, mono + /// Color theme for the output (see 'am help theme' for the list) #[arg(long, global = true, value_name = "THEME")] pub theme: Option, diff --git a/src/config.rs b/src/config.rs index 21a9ab8..1896f61 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,8 +66,8 @@ pub struct Settings { /// GitHub repository (owner/repo) used by the self-update command. #[serde(default)] pub self_update_repo: Option, - /// Color theme for the output: default, ocean, sunset, forest, dracula, - /// mono (the --theme flag overrides it). + /// Color theme for the output (see 'am help theme' for the list; the + /// --theme flag overrides it). #[serde(default)] pub theme: Option, } diff --git a/src/help.rs b/src/help.rs index cffe5a4..bc91426 100644 --- a/src/help.rs +++ b/src/help.rs @@ -103,7 +103,7 @@ const GLOBAL_FLAGS: &[HelpFlag] = &[ short: "", long: "--theme", value: "THEME", - desc: "Color theme for the output: default, ocean, sunset, forest, dracula, mono", + desc: "Color theme for the output (see 'am help theme' for the list)", }, ]; @@ -517,7 +517,7 @@ pub static HELP_SPECS: &[HelpSpec] = &[ flags: &[], subcommands: &[], parameters: &[ - HelpParam { name: "name", typ: "string", desc: "Theme to switch to (default, ocean, sunset, forest, dracula, mono); omit to list them" }, + HelpParam { name: "name", typ: "string", desc: "Theme to switch to; omit to list every available theme" }, ], io: None, examples: &[ diff --git a/src/repl.rs b/src/repl.rs index eb02779..e2c4c8a 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -572,11 +572,16 @@ pub fn banner_box( .map(|t| t.name) .filter(|n| *n != theme.name) .collect(); - rows.push(inner(dim( - &format!(" ⇄ {}", theme_others.join(" · ")), - theme, - color, - ))); + // The full list may not fit one line: wrap it into two ⇄ lines. + let half = theme_others.len().div_ceil(2); + for (i, chunk) in theme_others.chunks(half).enumerate() { + let line = format!( + "{}⇄ {}", + if i == 0 { " " } else { " " }, + chunk.join(" · ") + ); + rows.push(inner(dim(&line, theme, color))); + } rows.push(inner(dim( " 'am --theme ' to switch · settings.theme in the config", theme, @@ -1623,13 +1628,13 @@ mod tests { // The splash screen shows the active theme and every other theme. assert!(text.contains("Theme")); assert!(text.contains("◉ default")); - assert!(text.contains("ocean")); - assert!(text.contains("sunset")); - assert!(text.contains("forest")); - assert!(text.contains("dracula")); - assert!(text.contains("mono")); assert!(text.contains("theme default")); assert!(text.contains("--theme ")); + for t in crate::theme::THEMES { + if t.name != "default" { + assert!(text.contains(t.name), "banner misses theme {}", t.name); + } + } } #[test] diff --git a/src/theme.rs b/src/theme.rs index 6460ef4..faa4689 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -106,6 +106,132 @@ pub static THEMES: &[Theme] = &[ key: "38;5;141", value: "1", }, + Theme { + name: "nord", + title: "Nord (arctic frost)", + header: "1;38;5;109", + accent: "1;38;5;108", + info: "38;5;110", + dim: "2", + border: "38;5;109", + warn: "38;5;222", + error: "38;5;167", + ok: "1;38;5;108", + key: "38;5;110", + value: "1", + }, + Theme { + name: "solarized", + title: "Solarized (dark)", + header: "1;38;5;32", + accent: "1;38;5;100", + info: "38;5;37", + dim: "2", + border: "38;5;102", + warn: "38;5;136", + error: "38;5;160", + ok: "1;38;5;100", + key: "38;5;37", + value: "1", + }, + Theme { + name: "gruvbox", + title: "Gruvbox (retro warm)", + header: "1;38;5;109", + accent: "1;38;5;142", + info: "38;5;66", + dim: "2", + border: "38;5;59", + warn: "38;5;214", + error: "38;5;167", + ok: "1;38;5;142", + key: "38;5;108", + value: "1", + }, + Theme { + name: "catppuccin", + title: "Catppuccin (pastel)", + header: "1;38;5;111", + accent: "1;38;5;114", + info: "38;5;117", + dim: "2", + border: "38;5;111", + warn: "38;5;229", + error: "38;5;210", + ok: "1;38;5;114", + key: "38;5;140", + value: "1", + }, + Theme { + name: "tokyo-night", + title: "Tokyo Night (neon)", + header: "1;38;5;111", + accent: "1;38;5;114", + info: "38;5;117", + dim: "2", + border: "38;5;111", + warn: "38;5;215", + error: "38;5;204", + ok: "1;38;5;114", + key: "38;5;141", + value: "1", + }, + Theme { + name: "monokai", + title: "Monokai (classic IDE)", + header: "1;38;5;81", + accent: "1;38;5;148", + info: "38;5;81", + dim: "2", + border: "38;5;81", + warn: "38;5;185", + error: "38;5;197", + ok: "1;38;5;148", + key: "38;5;141", + value: "1", + }, + Theme { + name: "matrix", + title: "Matrix (green phosphor)", + header: "1;38;5;46", + accent: "1;38;5;40", + info: "38;5;34", + dim: "2", + border: "38;5;34", + warn: "38;5;190", + error: "38;5;196", + ok: "1;38;5;40", + key: "38;5;40", + value: "1", + }, + Theme { + name: "rose-pine", + title: "Rose Pine (elegant pink)", + header: "1;38;5;183", + accent: "1;38;5;216", + info: "38;5;117", + dim: "2", + border: "38;5;183", + warn: "38;5;180", + error: "38;5;204", + ok: "1;38;5;216", + key: "38;5;183", + value: "1", + }, + Theme { + name: "amber", + title: "Amber (retro CRT)", + header: "1;38;5;214", + accent: "1;38;5;215", + info: "38;5;178", + dim: "2", + border: "38;5;172", + warn: "38;5;220", + error: "38;5;196", + ok: "1;38;5;215", + key: "38;5;214", + value: "1", + }, Theme { name: "mono", title: "Mono (no colors)", @@ -241,6 +367,32 @@ mod tests { } } + #[test] + fn theme_names_are_unique() { + let mut names: Vec<&str> = THEMES.iter().map(|t| t.name).collect(); + names.sort(); + names.dedup(); + assert_eq!(names.len(), THEMES.len(), "duplicate theme names"); + } + + #[test] + fn extended_catalog_is_findable() { + for name in [ + "nord", + "solarized", + "gruvbox", + "catppuccin", + "tokyo-night", + "monokai", + "matrix", + "rose-pine", + "amber", + ] { + assert!(find(name).is_some(), "missing theme {name}"); + } + assert_eq!(THEMES.len(), 15); + } + #[test] fn frame_paints_with_the_theme_border_color() { let t = find("ocean").unwrap();