Generalize --detect-color-scheme to --color-scheme

This commit is contained in:
Tau Gärtli 2024-07-18 17:36:57 +02:00
parent abf9dada04
commit b9b981f657
No known key found for this signature in database
11 changed files with 140 additions and 69 deletions

View File

@ -32,7 +32,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
[CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'When to use colors (*auto*, never, always).') [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'When to use colors (*auto*, never, always).')
[CompletionResult]::new('--italic-text', 'italic-text', [CompletionResultType]::ParameterName, 'Use italics in output (always, *never*)') [CompletionResult]::new('--italic-text', 'italic-text', [CompletionResultType]::ParameterName, 'Use italics in output (always, *never*)')
[CompletionResult]::new('--decorations', 'decorations', [CompletionResultType]::ParameterName, 'When to show the decorations (*auto*, never, always).') [CompletionResult]::new('--decorations', 'decorations', [CompletionResultType]::ParameterName, 'When to show the decorations (*auto*, never, always).')
[CompletionResult]::new('--detect-color-scheme', 'detect-color-scheme', [CompletionResultType]::ParameterName, 'When to detect the terminal''s color scheme (*auto*, never, always).') [CompletionResult]::new('--color-scheme', 'color-scheme', [CompletionResultType]::ParameterName, 'Whether to choose a dark or light syntax highlighting theme (*auto*, auto:always, dark, light, system).')
[CompletionResult]::new('--paging', 'paging', [CompletionResultType]::ParameterName, 'Specify when to use the pager, or use `-P` to disable (*auto*, never, always).') [CompletionResult]::new('--paging', 'paging', [CompletionResultType]::ParameterName, 'Specify when to use the pager, or use `-P` to disable (*auto*, never, always).')
[CompletionResult]::new('--pager', 'pager', [CompletionResultType]::ParameterName, 'Determine which pager to use.') [CompletionResult]::new('--pager', 'pager', [CompletionResultType]::ParameterName, 'Determine which pager to use.')
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').') [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').')

View File

@ -100,10 +100,12 @@ _bat() {
COMPREPLY=($(compgen -W "auto never character" -- "$cur")) COMPREPLY=($(compgen -W "auto never character" -- "$cur"))
return 0 return 0
;; ;;
--color | --decorations | --paging | --detect-color-scheme) --color | --decorations | --paging)
COMPREPLY=($(compgen -W "auto never always" -- "$cur")) COMPREPLY=($(compgen -W "auto never always" -- "$cur"))
return 0 return 0
;; ;;
--color-scheme)
COMPREPLY=($(compgen -W "auto auto:always dark light system" -- "$cur"))
--italic-text) --italic-text)
COMPREPLY=($(compgen -W "always never" -- "$cur")) COMPREPLY=($(compgen -W "always never" -- "$cur"))
return 0 return 0
@ -166,7 +168,6 @@ _bat() {
--color --color
--italic-text --italic-text
--decorations --decorations
--detect-color-scheme
--force-colorization --force-colorization
--paging --paging
--pager --pager
@ -175,6 +176,7 @@ _bat() {
--theme --theme
--theme-dark --theme-dark
--theme-light --theme-light
--color-scheme
--list-themes --list-themes
--squeeze-blank --squeeze-blank
--squeeze-limit --squeeze-limit

View File

@ -99,7 +99,13 @@ set -l color_opts '
' '
set -l decorations_opts $color_opts set -l decorations_opts $color_opts
set -l paging_opts $color_opts set -l paging_opts $color_opts
set -l detect_color_scheme_opts $color_opts set -l color_scheme_opts "
auto\t'Use the terminal\'s color scheme if the output is not redirected (default)'
auto:always\t'Always use the terminal\'s color scheme'
dark\t'Use a dark syntax highlighting theme'
light\t'Use a light syntax highlighting theme'
system\t'Query the OS for its color scheme (macOS only)'
"
# Include some examples so we can indicate the default. # Include some examples so we can indicate the default.
set -l pager_opts ' set -l pager_opts '
@ -144,7 +150,7 @@ complete -c $bat -l config-file -f -d "Display location of configuration file" -
complete -c $bat -l decorations -x -a "$decorations_opts" -d "When to use --style decorations" -n __bat_no_excl_args complete -c $bat -l decorations -x -a "$decorations_opts" -d "When to use --style decorations" -n __bat_no_excl_args
complete -c $bat -l detect-color-scheme -x -a "$detect_color_scheme_opts" -d "When to detect the terminal's color scheme" -n __bat_no_excl_args complete -c $bat -l color-scheme -x -a "$color_scheme_opts" -d "Whether to choose a dark or light syntax highlighting theme" -n __bat_no_excl_args
complete -c $bat -l diagnostic -d "Print diagnostic info for bug reports" -n __fish_is_first_arg complete -c $bat -l diagnostic -d "Print diagnostic info for bug reports" -n __fish_is_first_arg

View File

@ -40,7 +40,7 @@ _{{PROJECT_EXECUTABLE}}_main() {
--color='[specify when to use colors]:when:(auto never always)' --color='[specify when to use colors]:when:(auto never always)'
--italic-text='[use italics in output]:when:(always never)' --italic-text='[use italics in output]:when:(always never)'
--decorations='[specify when to show the decorations]:when:(auto never always)' --decorations='[specify when to show the decorations]:when:(auto never always)'
--detect-color-scheme="[specify when to detect the terminal's color scheme]:when:(auto never always)" --color-scheme="[whether to choose a dark or light syntax highlighting theme]:scheme:(auto auto:always dark light system)"
--paging='[specify when to use the pager]:when:(auto never always)' --paging='[specify when to use the pager]:when:(auto never always)'
'(-m --map-syntax)'{-m+,--map-syntax=}'[map a glob pattern to an existing syntax name]: :->syntax-maps' '(-m --map-syntax)'{-m+,--map-syntax=}'[map a glob pattern to an existing syntax name]: :->syntax-maps'
'(--theme)'--theme='[set the color theme for syntax highlighting]:theme:->themes' '(--theme)'--theme='[set the color theme for syntax highlighting]:theme:->themes'

View File

@ -114,22 +114,22 @@ Options:
add the '--theme="..."' option to the configuration file or export the BAT_THEME add the '--theme="..."' option to the configuration file or export the BAT_THEME
environment variable (e.g.: export BAT_THEME="..."). environment variable (e.g.: export BAT_THEME="...").
--detect-color-scheme <when> --color-scheme <scheme>
Specify when to query the terminal for its colors in order to pick an appropriate syntax Specify whether to choose a dark or light syntax highlighting theme. Use '--theme-light'
highlighting theme. Use '--theme-light' and '--theme-dark' (or the environment variables and '--theme-dark' (or the environment variables BAT_THEME_LIGHT and BAT_THEME_DARK) to
BAT_THEME_LIGHT and BAT_THEME_DARK) to configure which themes are picked. You may also use configure which themes are picked. You may also use '--theme' to set a theme that is used
'--theme' to set a theme that is used regardless of the terminal's colors. regardless of this choice.
Possible values: Possible values:
* auto (default): * auto (default):
Only query the terminals colors if the output is not redirected. This is to prevent Query the terminals for its color scheme if the output is not redirected. This is to
race conditions with pagers such as less. prevent race conditions with pagers such as less.
* never * 'auto:always':
Never query the terminal for its colors and assume that the terminal has a dark Always query the terminal for its color scheme, regardless of whether or not the
background. output is redirected.
* always * dark: Use a dark syntax highlighting theme.
Always query the terminal for its colors, regardless of whether or not the output is * light: Use a light syntax highlighting theme.
redirected. * system: Query the OS for its color scheme. Only works on macOS.
--theme-light <theme> --theme-light <theme>
Sets the theme name for syntax highlighting used when the terminal uses a light Sets the theme name for syntax highlighting used when the terminal uses a light

View File

@ -41,8 +41,8 @@ Options:
Use the specified syntax for files matching the glob pattern ('*.cpp:C++'). Use the specified syntax for files matching the glob pattern ('*.cpp:C++').
--theme <theme> --theme <theme>
Set the color theme for syntax highlighting. Set the color theme for syntax highlighting.
--detect-color-scheme <when> --color-scheme <scheme>
Specify when to query the terminal for its colors. Specify whether to choose a dark or light theme.
--theme-light <theme> --theme-light <theme>
Sets the color theme for syntax highlighting used for light backgrounds. Sets the color theme for syntax highlighting used for light backgrounds.
--theme-dark <theme> --theme-dark <theme>

View File

@ -9,7 +9,7 @@ use crate::{
config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars}, config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars},
}; };
use bat::style::StyleComponentList; use bat::style::StyleComponentList;
use bat::theme::{theme, DetectColorScheme, ThemeOptions, ThemeRequest}; use bat::theme::{theme, ColorSchemePreference, DetectColorScheme, ThemeOptions, ThemeRequest};
use bat::StripAnsiMode; use bat::StripAnsiMode;
use clap::ArgMatches; use clap::ArgMatches;
@ -431,20 +431,22 @@ impl App {
theme, theme,
theme_dark, theme_dark,
theme_light, theme_light,
detect_color_scheme: self.detect_color_scheme(), color_scheme: self.color_scheme_preference(),
} }
} }
pub(crate) fn detect_color_scheme(&self) -> DetectColorScheme { pub(crate) fn color_scheme_preference(&self) -> ColorSchemePreference {
match self match self
.matches .matches
.get_one::<String>("detect-color-scheme") .get_one::<String>("color-scheme")
.map(|s| s.as_str()) .map(|s| s.as_str())
{ {
Some("auto") => DetectColorScheme::Auto, Some("auto") => ColorSchemePreference::Auto(DetectColorScheme::Auto),
Some("never") => DetectColorScheme::Never, Some("auto:always") => ColorSchemePreference::Auto(DetectColorScheme::Always),
Some("always") => DetectColorScheme::Always, Some("dark") => ColorSchemePreference::Dark,
_ => unreachable!("other values for --detect-color-scheme are not allowed"), Some("light") => ColorSchemePreference::Light,
Some("system") => ColorSchemePreference::System,
_ => unreachable!("other values for --color-scheme are not allowed"),
} }
} }
} }

View File

@ -384,30 +384,30 @@ pub fn build_app(interactive_output: bool) -> Command {
), ),
) )
.arg( .arg(
Arg::new("detect-color-scheme") Arg::new("color-scheme")
.long("detect-color-scheme") .long("color-scheme")
.overrides_with("detect-color-scheme") .overrides_with("color-scheme")
.value_name("when") .value_name("scheme")
.value_parser(["auto", "never", "always"]) .value_parser(["auto", "auto:always", "dark", "light", "system"])
.default_value("auto") .default_value("auto")
.hide_default_value(true) .hide_default_value(true)
.help("Specify when to query the terminal for its colors.") .help("Specify whether to choose a dark or light theme.")
.long_help( .long_help(
"Specify when to query the terminal for its colors \ "Specify whether to choose a dark or light syntax highlighting theme. \
in order to pick an appropriate syntax highlighting theme. \
Use '--theme-light' and '--theme-dark' (or the environment variables \ Use '--theme-light' and '--theme-dark' (or the environment variables \
BAT_THEME_LIGHT and BAT_THEME_DARK) to configure which themes are picked. \ BAT_THEME_LIGHT and BAT_THEME_DARK) to configure which themes are picked. \
You may also use '--theme' to set a theme that is used regardless of the terminal's colors.\n\n\ You may also use '--theme' to set a theme that is used regardless of this choice.\n\n\
Possible values:\n\ Possible values:\n\
* auto (default):\n \ * auto (default):\n \
Only query the terminals colors if the output is not redirected. \ Query the terminals for its color scheme if the output is not redirected. \
This is to prevent race conditions with pagers such as less.\n\ This is to prevent race conditions with pagers such as less.\n\
* never\n \ * 'auto:always':\n \
Never query the terminal for its colors \ Always query the terminal for its color scheme, \
and assume that the terminal has a dark background.\n\ regardless of whether or not the output is redirected.\n\
* always\n \ * dark: Use a dark syntax highlighting theme.\n\
Always query the terminal for its colors, \ * light: Use a light syntax highlighting theme.\n\
regardless of whether or not the output is redirected."), * system: Query the OS for its color scheme. Only works on macOS.\n\
"),
) )
.arg( .arg(
Arg::new("theme-light") Arg::new("theme-light")

View File

@ -35,7 +35,7 @@ use bat::{
error::*, error::*,
input::Input, input::Input,
style::{StyleComponent, StyleComponents}, style::{StyleComponent, StyleComponents},
theme::{color_scheme, default_theme, ColorScheme, DetectColorScheme}, theme::{color_scheme, default_theme, ColorScheme, ColorSchemePreference},
MappingTarget, PagingMode, MappingTarget, PagingMode,
}; };
@ -193,7 +193,7 @@ pub fn list_themes(
cfg: &Config, cfg: &Config,
config_dir: &Path, config_dir: &Path,
cache_dir: &Path, cache_dir: &Path,
detect_color_scheme: DetectColorScheme, color_scheme_pref: ColorSchemePreference,
) -> Result<()> { ) -> Result<()> {
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?; let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
let mut config = cfg.clone(); let mut config = cfg.clone();
@ -205,7 +205,7 @@ pub fn list_themes(
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let default_theme_name = default_theme(color_scheme(detect_color_scheme)); let default_theme_name = default_theme(color_scheme(color_scheme_pref));
for theme in assets.themes() { for theme in assets.themes() {
let default_theme_info = if default_theme_name == theme { let default_theme_info = if default_theme_name == theme {
" (default)" " (default)"
@ -380,7 +380,12 @@ fn run() -> Result<bool> {
}; };
run_controller(inputs, &plain_config, cache_dir) run_controller(inputs, &plain_config, cache_dir)
} else if app.matches.get_flag("list-themes") { } else if app.matches.get_flag("list-themes") {
list_themes(&config, config_dir, cache_dir, app.detect_color_scheme())?; list_themes(
&config,
config_dir,
cache_dir,
app.color_scheme_preference(),
)?;
Ok(true) Ok(true)
} else if app.matches.get_flag("config-file") { } else if app.matches.get_flag("config-file") {
println!("{}", config_file().to_string_lossy()); println!("{}", config_file().to_string_lossy());

View File

@ -20,8 +20,8 @@ pub const fn default_theme(color_scheme: ColorScheme) -> &'static str {
} }
/// Detects the color scheme from the terminal. /// Detects the color scheme from the terminal.
pub fn color_scheme(when: DetectColorScheme) -> ColorScheme { pub fn color_scheme(preference: ColorSchemePreference) -> ColorScheme {
detect(when, &TerminalColorSchemeDetector).unwrap_or_default() color_scheme_impl(preference, &TerminalColorSchemeDetector)
} }
/// Options for configuring the theme used for syntax highlighting. /// Options for configuring the theme used for syntax highlighting.
@ -34,8 +34,8 @@ pub struct ThemeOptions {
pub theme_dark: Option<ThemeRequest>, pub theme_dark: Option<ThemeRequest>,
/// The theme to use in case the terminal uses a light background with dark text. /// The theme to use in case the terminal uses a light background with dark text.
pub theme_light: Option<ThemeRequest>, pub theme_light: Option<ThemeRequest>,
/// Whether or not to test if the terminal is dark or light by querying for its colors. /// How to choose between dark and light.
pub detect_color_scheme: DetectColorScheme, pub color_scheme: ColorSchemePreference,
} }
/// The name of a theme or the default theme. /// The name of a theme or the default theme.
@ -73,6 +73,25 @@ impl ThemeRequest {
} }
} }
/// How to choose between dark and light.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ColorSchemePreference {
/// Detect the color scheme from the terminal.
Auto(DetectColorScheme),
/// Use a dark theme.
Dark,
/// Use a light theme.
Light,
/// Detect the color scheme from the OS instead (macOS only).
System,
}
impl Default for ColorSchemePreference {
fn default() -> Self {
Self::Auto(DetectColorScheme::default())
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DetectColorScheme { pub enum DetectColorScheme {
/// Only query the terminal for its colors when appropriate (i.e. when the the output is not redirected). /// Only query the terminal for its colors when appropriate (i.e. when the the output is not redirected).
@ -80,8 +99,6 @@ pub enum DetectColorScheme {
Auto, Auto,
/// Always query the terminal for its colors. /// Always query the terminal for its colors.
Always, Always,
/// Never query the terminal for its colors.
Never,
} }
/// The color scheme used to pick a fitting theme. Defaults to [`ColorScheme::Dark`]. /// The color scheme used to pick a fitting theme. Defaults to [`ColorScheme::Dark`].
@ -92,13 +109,25 @@ pub enum ColorScheme {
Light, Light,
} }
fn color_scheme_impl(
pref: ColorSchemePreference,
detector: &dyn ColorSchemeDetector,
) -> ColorScheme {
match pref {
ColorSchemePreference::Auto(when) => detect(when, detector).unwrap_or_default(),
ColorSchemePreference::Dark => ColorScheme::Dark,
ColorSchemePreference::Light => ColorScheme::Light,
ColorSchemePreference::System => color_scheme_from_system().unwrap_or_default(),
}
}
fn theme_from_detector(options: ThemeOptions, detector: &dyn ColorSchemeDetector) -> String { fn theme_from_detector(options: ThemeOptions, detector: &dyn ColorSchemeDetector) -> String {
// Implementation note: This function is mostly pure (i.e. it has no side effects) for the sake of testing. // Implementation note: This function is mostly pure (i.e. it has no side effects) for the sake of testing.
// All the side effects (e.g. querying the terminal for its colors) are performed in the detector. // All the side effects (e.g. querying the terminal for its colors) are performed in the detector.
if let Some(theme) = options.theme { if let Some(theme) = options.theme {
theme.into_theme(ColorScheme::default()) theme.into_theme(ColorScheme::default())
} else { } else {
let color_scheme = detect(options.detect_color_scheme, detector).unwrap_or_default(); let color_scheme = color_scheme_impl(options.color_scheme, detector);
choose_theme(options, color_scheme) choose_theme(options, color_scheme)
.map(|t| t.into_theme(color_scheme)) .map(|t| t.into_theme(color_scheme))
.unwrap_or_else(|| default_theme(color_scheme).to_owned()) .unwrap_or_else(|| default_theme(color_scheme).to_owned())
@ -116,7 +145,6 @@ fn detect(when: DetectColorScheme, detector: &dyn ColorSchemeDetector) -> Option
let should_detect = match when { let should_detect = match when {
DetectColorScheme::Auto => detector.should_detect(), DetectColorScheme::Auto => detector.should_detect(),
DetectColorScheme::Always => true, DetectColorScheme::Always => true,
DetectColorScheme::Never => false,
}; };
should_detect.then(|| detector.detect()).flatten() should_detect.then(|| detector.detect()).flatten()
} }
@ -152,6 +180,31 @@ impl ColorSchemeDetector for TerminalColorSchemeDetector {
} }
} }
#[cfg(not(target_os = "macos"))]
fn color_scheme_from_system() -> Option<ColorScheme> {
None
}
#[cfg(target_os = "macos")]
fn color_scheme_from_system() -> Option<ColorScheme> {
const PREFERENCES_FILE: &str = "Library/Preferences/.GlobalPreferences.plist";
const STYLE_KEY: &str = "AppleInterfaceStyle";
let preferences_file = home::home_dir()
.map(|home| home.join(PREFERENCES_FILE))
.expect("Could not get home directory");
match plist::Value::from_file(preferences_file).map(|file| file.into_dictionary()) {
Ok(Some(preferences)) => match preferences.get(STYLE_KEY).and_then(|val| val.as_string()) {
Some(value) if value == "Dark" => Some(ColorScheme::Dark),
// If the key does not exist, then light theme is currently in use.
Some(_) | None => Some(ColorScheme::Light),
},
// Unreachable, in theory. All macOS users have a home directory and preferences file setup.
Ok(None) | Err(_) => None,
}
}
#[cfg(test)] #[cfg(test)]
impl ColorSchemeDetector for Option<ColorScheme> { impl ColorSchemeDetector for Option<ColorScheme> {
fn should_detect(&self) -> bool { fn should_detect(&self) -> bool {
@ -166,6 +219,7 @@ impl ColorSchemeDetector for Option<ColorScheme> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ColorScheme::*; use super::ColorScheme::*;
use super::ColorSchemePreference as Pref;
use super::DetectColorScheme::*; use super::DetectColorScheme::*;
use super::*; use super::*;
use std::cell::Cell; use std::cell::Cell;
@ -175,15 +229,17 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn not_called_for_never() { fn not_called_for_dark_or_light() {
for pref in [Pref::Dark, Pref::Light] {
let detector = DetectorStub::should_detect(Some(Dark)); let detector = DetectorStub::should_detect(Some(Dark));
let options = ThemeOptions { let options = ThemeOptions {
detect_color_scheme: Never, color_scheme: pref,
..Default::default() ..Default::default()
}; };
_ = theme_from_detector(options, &detector); _ = theme_from_detector(options, &detector);
assert!(!detector.was_called.get()); assert!(!detector.was_called.get());
} }
}
#[test] #[test]
fn called_for_always() { fn called_for_always() {
@ -193,7 +249,7 @@ mod tests {
]; ];
for detector in detectors { for detector in detectors {
let options = ThemeOptions { let options = ThemeOptions {
detect_color_scheme: Always, color_scheme: Pref::Auto(Always),
..Default::default() ..Default::default()
}; };
_ = theme_from_detector(options, &detector); _ = theme_from_detector(options, &detector);

View File

@ -279,7 +279,7 @@ fn list_themes_with_colors() {
bat() bat()
.arg("--color=always") .arg("--color=always")
.arg("--detect-color-scheme=never") .arg("--color-scheme=dark")
.arg("--list-themes") .arg("--list-themes")
.assert() .assert()
.success() .success()
@ -296,7 +296,7 @@ fn list_themes_without_colors() {
bat() bat()
.arg("--color=never") .arg("--color=never")
.arg("--detect-color-scheme=never") .arg("--color-scheme=dark")
.arg("--list-themes") .arg("--list-themes")
.assert() .assert()
.success() .success()