1
0
mirror of https://github.com/sharkdp/bat.git synced 2025-09-02 11:22:30 +01:00

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
11 changed files with 140 additions and 69 deletions

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);