Skip to main content

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}