cli_ui/prompt/settings.rs
1//! Global prompt settings — ports `@clack/core`'s `settings.ts`.
2//!
3//! Right now this is a thin holder for the messages that the prompts use when
4//! the user cancels or an error stops a spinner. The defaults match clack.
5//! Override via [`update`] before running any prompts:
6//!
7//! ```no_run
8//! cli_ui::prompt::settings::update(|s| s.cancel = "Aborted.".into());
9//! ```
10
11use crate::styles::{BADGE_INTRO, BOLD, CYAN, DIM, ERR, OK, WHITE, WHITE_BOLD, YELLOW};
12use anstyle::Style;
13use std::sync::RwLock;
14
15/// Every color slot the prompt theme reads. Override via [`update_colors`].
16#[derive(Clone, Copy)]
17pub struct Colors {
18 /// Small accent glyphs — radio dots (`●`/`○`), checkboxes (`◼`/`◻`),
19 /// scroll arrows (`❯`), the active prompt diamond (`◆`).
20 pub accent: Style,
21 /// Highlight color for the *currently active* prompt frame — the `│`
22 /// running down the side of the input line and the closing `└` glyph.
23 /// Once the prompt is submitted, the frame fades back to `dim`.
24 pub active: Style,
25 /// Live user input — typed text inside text/secret/multiline/path/auto,
26 /// plus the inline cursor block. White by default so what you type looks
27 /// like what you typed.
28 pub input: Style,
29 /// Answered prompt glyph (`◇`) and success markers.
30 pub success: Style,
31 /// Error glyph (`▲`), error frame bar, error message text.
32 pub error: Style,
33 /// Cancel glyph (`■`) and cancel banner — used by Ctrl+C handler.
34 pub cancel: Style,
35 /// Frame bars (`│`, `┌`, `└`), placeholder text, idle option labels.
36 pub dim: Style,
37 /// Active question/answer text style — applied to the header line.
38 pub header: Style,
39 /// Fallback foreground when bold headers are off.
40 pub header_plain: Style,
41 /// Section title style — used by [`outro`](super::outro) and
42 /// [`note`](super::note). Defaults to a pure bold attribute (no fg
43 /// override) so it stays visibly bolder than regular text on every
44 /// terminal that does not collapse `bold` on bright colors.
45 pub title: Style,
46 /// Style for the [`intro`](super::intro) pill — the clack-style badge
47 /// that frames the session title. Defaults to cyan background, black
48 /// text, bold. The intro renderer pads the text with one space on each
49 /// side, so the badge always reads as ` <title> `.
50 pub intro_badge: Style,
51}
52
53impl Default for Colors {
54 fn default() -> Self {
55 Self {
56 accent: CYAN,
57 active: CYAN,
58 input: WHITE,
59 success: OK,
60 error: YELLOW,
61 cancel: ERR,
62 dim: DIM,
63 header: WHITE_BOLD,
64 header_plain: WHITE,
65 title: BOLD,
66 intro_badge: BADGE_INTRO,
67 }
68 }
69}
70
71/// Convenience: pull just the [`Colors`] from the current settings.
72pub fn colors() -> Colors {
73 get().colors
74}
75
76/// Mutate just the [`Colors`] block without touching other settings.
77pub fn update_colors<F: FnOnce(&mut Colors)>(f: F) {
78 update(|s| f(&mut s.colors))
79}
80
81/// Replace the colour palette wholesale — handy for shipping named themes.
82///
83/// ```no_run
84/// use cli_ui::prompt::settings::{set_colors, Colors};
85///
86/// // A monochrome "no-color" palette built off the default and then tweaked.
87/// let mut c = Colors::default();
88/// let bold = anstyle::Style::new().bold();
89/// c.accent = bold; c.success = bold; c.error = bold;
90/// c.cancel = bold; c.title = bold; c.header = bold;
91/// set_colors(c);
92/// ```
93pub fn set_colors(c: Colors) {
94 update(|s| s.colors = c);
95}
96
97/// Global prompt settings — colour palette, default messages, key
98/// behaviours. Read with [`get`], mutate with [`update`] or
99/// [`update_colors`], replace the colours wholesale with [`set_colors`].
100#[derive(Clone)]
101pub struct Settings {
102 /// Default message printed when a spinner is cancelled (Ctrl-C, etc.).
103 pub cancel: String,
104 /// Default message printed when a spinner stops with an error.
105 pub error: String,
106 /// When `false`, prompts skip the connector `│` bars between rows.
107 pub with_guide: bool,
108 /// When `true`, h/j/k/l act as left/down/up/right inside list prompts.
109 pub vim_keys: bool,
110 /// When `true`, `.hint(...)` text is rendered under each prompt.
111 /// Off by default — call `update(|s| s.show_hints = true)` to opt in.
112 pub show_hints: bool,
113 /// When `true`, prompt question text (the `◆ <header>` line) is bold.
114 /// On by default — matches clack, gum, and inquirer.
115 pub bold_header: bool,
116 /// Color palette used by the prompt renderer.
117 pub colors: Colors,
118}
119
120impl Default for Settings {
121 fn default() -> Self {
122 Self {
123 cancel: "Operation cancelled.".into(),
124 error: "Something went wrong.".into(),
125 with_guide: true,
126 vim_keys: true,
127 show_hints: false,
128 bold_header: true,
129 colors: Colors::default(),
130 }
131 }
132}
133
134static SETTINGS: RwLock<Option<Settings>> = RwLock::new(None);
135
136/// Get a snapshot of the current settings.
137pub fn get() -> Settings {
138 SETTINGS.read().unwrap().clone().unwrap_or_default()
139}
140
141/// Mutate the global settings in place.
142pub fn update<F: FnOnce(&mut Settings)>(f: F) {
143 let mut guard = SETTINGS.write().unwrap();
144 let mut s = guard.clone().unwrap_or_default();
145 f(&mut s);
146 *guard = Some(s);
147}