Skip to content

Commit c09e08a

Browse files
committed
Pick help's palette from the terminal's background, in teal
Help asks the terminal for its background (OSC 11) and uses a palette for a dark or a light one; other runs, and help where the terminal doesn't answer, keep the palette that reads on both. Only help asks: the answer is read off the terminal's input, and a command that prompts afterwards lost the answer typed ahead. Every colored run of text is bold. AGI-1169 AGI-1174
1 parent b3f4693 commit c09e08a

8 files changed

Lines changed: 318 additions & 79 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ that may never merge. They are not releases and are not listed here.
2525
sentence. Help text only; `--schema` always had the full description.
2626

2727
- Color reads on light and dark terminal themes alike. Tables, tips, the
28-
banner and help notes use a small fixed palette (Mapbox blue, gray, red)
29-
whose colors all clear 3.5:1 on common themes, instead of the
30-
terminal's own bright blue and dim, which fell below 2:1 on some light
31-
ones. A terminal not known to show 24-bit color gets bold and plain text
32-
instead. New `--no-color` flag turns color off, as `NO_COLOR` does, and
33-
`FORCE_COLOR=1` now keeps color in a pipe; either way off wins.
28+
banner and help use a small fixed palette (teal, gray, red) instead of
29+
the terminal's own bright blue and dim, which fell below 2:1 on some
30+
light themes; every colored run of text is bold. Help asks the terminal
31+
for its background and uses a palette made for a dark or a light one;
32+
everything else, and help where the terminal doesn't answer, uses one
33+
that reads on both. A terminal not known to show 24-bit color gets bold
34+
and plain text instead. New `--no-color` flag turns color off, as
35+
`NO_COLOR` does, and `FORCE_COLOR=1` now keeps color in a pipe; either
36+
way off wins.
3437

3538
- Top-level `mapbox --help` lists commands in groups (Maps and data,
3639
Search, Account, Coding agents, CLI), each with a line saying what it
@@ -39,7 +42,7 @@ that may never merge. They are not releases and are not listed here.
3942
Subcommand help uses clap's compact layout for `--help` as well as
4043
`-h`, with the command's full description at the top, and points to
4144
`mapbox --help` for the global options instead of repeating them; they
42-
still work on every command. Headings are blue; names to type and values
45+
still work on every command. Headings are teal; names to type and values
4346
to fill in are bold in the terminal's own foreground; notes are gray. clap's yellow and green in usage errors
4447
are bold now too.
4548
Help text only: no command, flag, exit code or result output changes.

‎Cargo.lock‎

Lines changed: 35 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ dirs = "5"
103103
# are needed for `parse_to_serde_value`, which decodes straight into a
104104
# `serde_json::Value`.
105105
jsonc-parser = { version = "0.34.0", features = ["serde", "serde_json"] }
106+
# Asks the terminal for its background color (OSC 11), so help can use the
107+
# palette for a dark or a light one. It reads the answer off the terminal,
108+
# which takes raw mode and a timed read — `unsafe` this crate does not
109+
# have — and it sends a second query every terminal answers, so one without
110+
# OSC 11 is known at once instead of after the timeout. Asked only by a run
111+
# that shows help; see src/output/theme.rs.
112+
terminal-colorsaurus = "1"
106113

107114
[dev-dependencies]
108115
# Integration tests build fake Mapbox tokens; same crate the CLI already uses.

‎README.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,10 @@ At a terminal, a run opens with a `mapbox · v<version>` line on stderr, and
472472
help, tables, labels and tips are in color. Neither reaches a pipe or a
473473
file. `-q`/`--quiet` (or `MAPBOX_QUIET=1`) hides the banner. `--no-color`
474474
or `NO_COLOR` turns color off; `FORCE_COLOR=1` keeps it on into a pipe, for
475-
a pager such as `less -R`, and `--no-color`/`NO_COLOR` win over it. The colors are fixed and chosen
476-
to read on light and dark themes alike; a terminal not known to show 24-bit
477-
color gets bold text instead.
475+
a pager such as `less -R`, and `--no-color`/`NO_COLOR` win over it. The
476+
colors are fixed and chosen to read on light and dark themes alike; help
477+
asks the terminal for its background to pick a palette for it. A terminal
478+
not known to show 24-bit color gets bold text instead.
478479

479480
### `--schema`
480481

‎src/help_layout.rs‎

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,26 @@ fn page_width() -> usize {
459459
detected.map_or(MAX_WIDTH, |w| w.min(MAX_WIDTH))
460460
}
461461

462+
/// Whether argv asks for help, read before clap parses it: `-h` or `--help`
463+
/// anywhere before `--`, or `help` as the first word that is not an option.
464+
/// The one kind of run allowed to ask the terminal for its background (see
465+
/// `output::theme::allow_background_query`). A miss costs only the fallback
466+
/// palette; a false match on a command that prompts would cost the answer
467+
/// typed ahead, which is why `help` counts only as the subcommand.
468+
pub fn help_requested(argv: &[std::ffi::OsString]) -> bool {
469+
let mut first_word = true;
470+
for arg in argv.iter().skip(1).filter_map(|arg| arg.to_str()) {
471+
match arg {
472+
"--" => return false,
473+
"-h" | "--help" => return true,
474+
"help" if first_word => return true,
475+
_ if !arg.starts_with('-') => first_word = false,
476+
_ => {}
477+
}
478+
}
479+
false
480+
}
481+
462482
/// The copy of the tree that parses, and so renders every subcommand's
463483
/// help: compact, and about the command itself.
464484
///
@@ -663,13 +683,15 @@ mod tests {
663683
assert!(missing.is_empty(), "{}", missing.join("\n"));
664684
}
665685

666-
/// Help and clap's errors use bold and the palette's fixed colors, never
686+
/// Help and clap's errors use bold and the palettes' fixed colors, never
667687
/// one of the sixteen a terminal theme decides — and without 24-bit
668688
/// color, no color at all. See `output::theme`.
669689
#[test]
670690
fn theme_safe_colors_only() {
671-
for truecolor in [true, false] {
672-
let app = app().styles(crate::output::theme::styles_for(truecolor));
691+
use crate::output::theme::{styles_in, Look, LOOKS};
692+
for &look in LOOKS {
693+
let truecolor = look != Look::Bold;
694+
let app = app().styles(styles_in(look));
673695
let parsing = for_parsing(apply(app));
674696
for argv in [
675697
&["mapbox", "--help"][..],
@@ -687,18 +709,18 @@ mod tests {
687709
let problems = unsafe_styles(&text, truecolor);
688710
assert!(
689711
problems.is_empty(),
690-
"{argv:?} (truecolor: {truecolor}) uses {problems:?}:\n{text}"
712+
"{argv:?} ({look:?}) uses {problems:?}:\n{text}"
691713
);
692714
}
693715
}
694716
}
695717

696718
/// The SGR parameters in `text` that are neither a reset, bold, nor —
697-
/// with `truecolor` — a 24-bit color from the palette.
719+
/// with `truecolor` — a 24-bit color from one of the palettes.
698720
fn unsafe_styles(text: &str, truecolor: bool) -> Vec<String> {
699-
let palette: Vec<String> = crate::output::theme::PALETTE
721+
let palette: Vec<String> = crate::output::theme::colors()
700722
.iter()
701-
.map(|(c, _)| format!("{};{};{}", c.0, c.1, c.2))
723+
.map(|c| format!("{};{};{}", c.0, c.1, c.2))
702724
.collect();
703725
let mut problems = Vec::new();
704726
for sequence in text.split("\x1b[").skip(1) {
@@ -734,6 +756,26 @@ mod tests {
734756
problems
735757
}
736758

759+
#[test]
760+
fn help_is_recognized_only_where_it_asks_for_help() {
761+
let asks = |args: &[&str]| {
762+
let argv: Vec<std::ffi::OsString> = std::iter::once("mapbox")
763+
.chain(args.iter().copied())
764+
.map(Into::into)
765+
.collect();
766+
help_requested(&argv)
767+
};
768+
assert!(asks(&["--help"]));
769+
assert!(asks(&["styles", "list", "-h"]));
770+
assert!(asks(&["help", "styles"]));
771+
assert!(asks(&["--no-color", "help"]));
772+
// A style named "help" is a value, not a request: deleting it
773+
// prompts, and the prompt must keep its answer.
774+
assert!(!asks(&["styles", "delete", "help"]));
775+
assert!(!asks(&["tilesets-cli", "--", "--help"]));
776+
assert!(!asks(&["styles", "list"]));
777+
}
778+
737779
fn shown(text: &str) -> Vec<String> {
738780
pieces(text)
739781
.iter()

‎src/main.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,9 @@ fn cli() -> u8 {
837837
if output::style::requested_off(&raw_argv) {
838838
output::style::turn_off();
839839
}
840+
if help_layout::help_requested(&raw_argv) {
841+
output::theme::allow_background_query(output::on_a_terminal());
842+
}
840843

841844
let mut app = build_app(&specs);
842845
// Here rather than in `build_app`, which tests call directly and which

‎src/output/mod.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,12 @@ fn tip_lines(tips: &[String], color: bool) -> Vec<String> {
352352
.collect()
353353
}
354354

355+
/// Whether either output stream is a terminal — something that can be asked
356+
/// for its background color (see `theme::allow_background_query`).
357+
pub fn on_a_terminal() -> bool {
358+
std::io::stdout().is_terminal() || std::io::stderr().is_terminal()
359+
}
360+
355361
/// Whether a result written to stdout may carry color. Only a terminal:
356362
/// `-o text > file` must leave a file with no escapes in it.
357363
pub fn result_in_color() -> bool {

0 commit comments

Comments
 (0)