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('--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('--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('--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++'').')

View File

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

View File

@ -99,7 +99,13 @@ set -l color_opts '
'
set -l decorations_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.
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 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

View File

@ -40,7 +40,7 @@ _{{PROJECT_EXECUTABLE}}_main() {
--color='[specify when to use colors]:when:(auto never always)'
--italic-text='[use italics in output]:when:(always never)'
--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)'
'(-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'

View File

@ -114,22 +114,22 @@ Options:
add the '--theme="..."' option to the configuration file or export the BAT_THEME
environment variable (e.g.: export BAT_THEME="...").
--detect-color-scheme <when>
Specify when to query the terminal for its colors in order to pick an appropriate syntax
highlighting theme. Use '--theme-light' and '--theme-dark' (or the environment variables
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.
--color-scheme <scheme>
Specify whether to choose a dark or light syntax highlighting theme. Use '--theme-light'
and '--theme-dark' (or the environment variables 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 this choice.
Possible values:
* auto (default):
Only query the terminals colors if the output is not redirected. This is to prevent
race conditions with pagers such as less.
* never
Never query the terminal for its colors and assume that the terminal has a dark
background.
* always
Always query the terminal for its colors, regardless of whether or not the output is
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.
* 'auto:always':
Always query the terminal for its color scheme, regardless of whether or not the
output is redirected.
* dark: Use a dark syntax highlighting theme.
* light: Use a light syntax highlighting theme.
* system: Query the OS for its color scheme. Only works on macOS.
--theme-light <theme>
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++').
--theme <theme>
Set the color theme for syntax highlighting.
--detect-color-scheme <when>
Specify when to query the terminal for its colors.
--color-scheme <scheme>
Specify whether to choose a dark or light theme.
--theme-light <theme>
Sets the color theme for syntax highlighting used for light backgrounds.
--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},
};
use bat::style::StyleComponentList;
use bat::theme::{theme, DetectColorScheme, ThemeOptions, ThemeRequest};
use bat::theme::{theme, ColorSchemePreference, DetectColorScheme, ThemeOptions, ThemeRequest};
use bat::StripAnsiMode;
use clap::ArgMatches;
@ -431,20 +431,22 @@ impl App {
theme,
theme_dark,
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
.matches
.get_one::<String>("detect-color-scheme")
.get_one::<String>("color-scheme")
.map(|s| s.as_str())
{
Some("auto") => DetectColorScheme::Auto,
Some("never") => DetectColorScheme::Never,
Some("always") => DetectColorScheme::Always,
_ => unreachable!("other values for --detect-color-scheme are not allowed"),
Some("auto") => ColorSchemePreference::Auto(DetectColorScheme::Auto),
Some("auto:always") => ColorSchemePreference::Auto(DetectColorScheme::Always),
Some("dark") => ColorSchemePreference::Dark,
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::new("detect-color-scheme")
.long("detect-color-scheme")
.overrides_with("detect-color-scheme")
.value_name("when")
.value_parser(["auto", "never", "always"])
Arg::new("color-scheme")
.long("color-scheme")
.overrides_with("color-scheme")
.value_name("scheme")
.value_parser(["auto", "auto:always", "dark", "light", "system"])
.default_value("auto")
.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(
"Specify when to query the terminal for its colors \
in order to pick an appropriate syntax highlighting theme. \
"Specify whether to choose a dark or light syntax highlighting theme. \
Use '--theme-light' and '--theme-dark' (or the environment variables \
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\
* auto (default):\n \
Only query the terminals colors if the output is not redirected. \
* auto (default):\n \
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\
* never\n \
Never query the terminal for its colors \
and assume that the terminal has a dark background.\n\
* always\n \
Always query the terminal for its colors, \
regardless of whether or not the output is redirected."),
* 'auto:always':\n \
Always query the terminal for its color scheme, \
regardless of whether or not the output is redirected.\n\
* dark: Use a dark syntax highlighting theme.\n\
* light: Use a light syntax highlighting theme.\n\
* system: Query the OS for its color scheme. Only works on macOS.\n\
"),
)
.arg(
Arg::new("theme-light")

View File

@ -35,7 +35,7 @@ use bat::{
error::*,
input::Input,
style::{StyleComponent, StyleComponents},
theme::{color_scheme, default_theme, ColorScheme, DetectColorScheme},
theme::{color_scheme, default_theme, ColorScheme, ColorSchemePreference},
MappingTarget, PagingMode,
};
@ -193,7 +193,7 @@ pub fn list_themes(
cfg: &Config,
config_dir: &Path,
cache_dir: &Path,
detect_color_scheme: DetectColorScheme,
color_scheme_pref: ColorSchemePreference,
) -> Result<()> {
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
let mut config = cfg.clone();
@ -205,7 +205,7 @@ pub fn list_themes(
let stdout = io::stdout();
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() {
let default_theme_info = if default_theme_name == theme {
" (default)"
@ -380,7 +380,12 @@ fn run() -> Result<bool> {
};
run_controller(inputs, &plain_config, cache_dir)
} 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)
} else if app.matches.get_flag("config-file") {
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.
pub fn color_scheme(when: DetectColorScheme) -> ColorScheme {
detect(when, &TerminalColorSchemeDetector).unwrap_or_default()
pub fn color_scheme(preference: ColorSchemePreference) -> ColorScheme {
color_scheme_impl(preference, &TerminalColorSchemeDetector)
}
/// Options for configuring the theme used for syntax highlighting.
@ -34,8 +34,8 @@ pub struct ThemeOptions {
pub theme_dark: Option<ThemeRequest>,
/// The theme to use in case the terminal uses a light background with dark text.
pub theme_light: Option<ThemeRequest>,
/// Whether or not to test if the terminal is dark or light by querying for its colors.
pub detect_color_scheme: DetectColorScheme,
/// How to choose between dark and light.
pub color_scheme: ColorSchemePreference,
}
/// 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)]
pub enum DetectColorScheme {
/// 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,
/// Always query the terminal for its colors.
Always,
/// Never query the terminal for its colors.
Never,
}
/// The color scheme used to pick a fitting theme. Defaults to [`ColorScheme::Dark`].
@ -92,13 +109,25 @@ pub enum ColorScheme {
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 {
// 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.
if let Some(theme) = options.theme {
theme.into_theme(ColorScheme::default())
} 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)
.map(|t| t.into_theme(color_scheme))
.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 {
DetectColorScheme::Auto => detector.should_detect(),
DetectColorScheme::Always => true,
DetectColorScheme::Never => false,
};
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)]
impl ColorSchemeDetector for Option<ColorScheme> {
fn should_detect(&self) -> bool {
@ -166,6 +219,7 @@ impl ColorSchemeDetector for Option<ColorScheme> {
#[cfg(test)]
mod tests {
use super::ColorScheme::*;
use super::ColorSchemePreference as Pref;
use super::DetectColorScheme::*;
use super::*;
use std::cell::Cell;
@ -175,14 +229,16 @@ mod tests {
use super::*;
#[test]
fn not_called_for_never() {
let detector = DetectorStub::should_detect(Some(Dark));
let options = ThemeOptions {
detect_color_scheme: Never,
..Default::default()
};
_ = theme_from_detector(options, &detector);
assert!(!detector.was_called.get());
fn not_called_for_dark_or_light() {
for pref in [Pref::Dark, Pref::Light] {
let detector = DetectorStub::should_detect(Some(Dark));
let options = ThemeOptions {
color_scheme: pref,
..Default::default()
};
_ = theme_from_detector(options, &detector);
assert!(!detector.was_called.get());
}
}
#[test]
@ -193,7 +249,7 @@ mod tests {
];
for detector in detectors {
let options = ThemeOptions {
detect_color_scheme: Always,
color_scheme: Pref::Auto(Always),
..Default::default()
};
_ = theme_from_detector(options, &detector);

View File

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