mirror of
https://github.com/sharkdp/bat.git
synced 2024-11-22 07:43:39 +01:00
Return theme alongside detected color scheme
This commit is contained in:
parent
89ce060183
commit
50958472e5
@ -254,7 +254,7 @@ impl App {
|
||||
Some("auto") => StripAnsiMode::Auto,
|
||||
_ => unreachable!("other values for --strip-ansi are not allowed"),
|
||||
},
|
||||
theme: theme(self.theme_options()),
|
||||
theme: theme(self.theme_options()).to_string(),
|
||||
visible_lines: match self.matches.try_contains_id("diff").unwrap_or_default()
|
||||
&& self.matches.get_flag("diff")
|
||||
{
|
||||
|
92
src/theme.rs
92
src/theme.rs
@ -1,13 +1,18 @@
|
||||
//! Utilities for choosing an appropriate theme for syntax highlighting.
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::fmt;
|
||||
use std::io::IsTerminal as _;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Chooses an appropriate theme or falls back to a default theme
|
||||
/// based on the user-provided options and the color scheme of the terminal.
|
||||
pub fn theme(options: ThemeOptions) -> String {
|
||||
theme_from_detector(options, &TerminalColorSchemeDetector)
|
||||
///
|
||||
/// Intentionally returns a [`ThemeResult`] instead of a simple string so
|
||||
/// that downstream consumers such as `delta` can easily apply their own
|
||||
/// default theme and can use the detected color scheme elsewhere.
|
||||
pub fn theme(options: ThemeOptions) -> ThemeResult {
|
||||
theme_impl(options, &TerminalColorSchemeDetector)
|
||||
}
|
||||
|
||||
/// The default theme, suitable for the given color scheme.
|
||||
@ -26,7 +31,7 @@ pub fn color_scheme(preference: ColorSchemePreference) -> ColorScheme {
|
||||
|
||||
/// Options for configuring the theme used for syntax highlighting.
|
||||
/// Used together with [`theme`].
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ThemeOptions {
|
||||
/// Always use this theme regardless of the terminal's background color.
|
||||
/// This corresponds with the `BAT_THEME` environment variable and the `--theme` option.
|
||||
@ -40,7 +45,14 @@ pub struct ThemeOptions {
|
||||
}
|
||||
|
||||
/// What theme should `bat` use?
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
///
|
||||
/// The easiest way to construct this is from a string:
|
||||
/// ```
|
||||
/// # use bat::theme::ThemePreference;
|
||||
/// # use std::str::FromStr as _;
|
||||
/// let preference = ThemePreference::from_str("auto:system").unwrap();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ThemePreference {
|
||||
/// Choose between [`ThemeOptions::theme_dark`] and [`ThemeOptions::theme_light`]
|
||||
/// based on the terminal's (or the OS') color scheme.
|
||||
@ -79,7 +91,7 @@ impl FromStr for ThemePreference {
|
||||
/// assert_eq!(ThemeName::Default, ThemeName::from_str("default").unwrap());
|
||||
/// assert_eq!(ThemeName::Named("example".to_string()), ThemeName::from_str("example").unwrap());
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ThemeName {
|
||||
Named(String),
|
||||
Default,
|
||||
@ -97,15 +109,6 @@ impl FromStr for ThemeName {
|
||||
}
|
||||
}
|
||||
|
||||
impl ThemeName {
|
||||
fn into_theme(self, color_scheme: ColorScheme) -> String {
|
||||
match self {
|
||||
ThemeName::Named(t) => t,
|
||||
ThemeName::Default => default_theme(color_scheme).to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How to choose between dark and light.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ColorSchemePreference {
|
||||
@ -142,6 +145,26 @@ pub enum ColorScheme {
|
||||
Light,
|
||||
}
|
||||
|
||||
/// The resolved theme and the color scheme as determined from
|
||||
/// the terminal, OS or fallback.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ThemeResult {
|
||||
/// The theme selected according to the [`ThemeOptions`].
|
||||
pub theme: ThemeName,
|
||||
/// Either the user's chosen color scheme, the terminal's color scheme, the OS's
|
||||
/// color scheme or `None` if the color scheme was not detected because the user chose a fixed theme.
|
||||
pub color_scheme: Option<ColorScheme>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ThemeResult {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.theme {
|
||||
ThemeName::Named(name) => f.write_str(name),
|
||||
ThemeName::Default => f.write_str(default_theme(self.color_scheme.unwrap_or_default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn color_scheme_impl(
|
||||
pref: ColorSchemePreference,
|
||||
detector: &dyn ColorSchemeDetector,
|
||||
@ -154,16 +177,21 @@ fn color_scheme_impl(
|
||||
}
|
||||
}
|
||||
|
||||
fn theme_from_detector(options: ThemeOptions, detector: &dyn ColorSchemeDetector) -> String {
|
||||
fn theme_impl(options: ThemeOptions, detector: &dyn ColorSchemeDetector) -> ThemeResult {
|
||||
// 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.
|
||||
match options.theme {
|
||||
ThemePreference::Fixed(theme_name) => theme_name.into_theme(ColorScheme::default()),
|
||||
ThemePreference::Auto(color_scheme_preference) => {
|
||||
let color_scheme = color_scheme_impl(color_scheme_preference, detector);
|
||||
choose_theme(options, color_scheme)
|
||||
.map(|t| t.into_theme(color_scheme))
|
||||
.unwrap_or_else(|| default_theme(color_scheme).to_owned())
|
||||
ThemePreference::Fixed(theme) => ThemeResult {
|
||||
theme,
|
||||
color_scheme: None,
|
||||
},
|
||||
ThemePreference::Auto(pref) => {
|
||||
let color_scheme = color_scheme_impl(pref, detector);
|
||||
let theme = choose_theme(options, color_scheme).unwrap_or(ThemeName::Default);
|
||||
ThemeResult {
|
||||
theme,
|
||||
color_scheme: Some(color_scheme),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -273,7 +301,7 @@ mod tests {
|
||||
theme: ThemePreference::Auto(pref),
|
||||
..Default::default()
|
||||
};
|
||||
_ = theme_from_detector(options, &detector);
|
||||
_ = theme_impl(options, &detector);
|
||||
assert!(!detector.was_called.get());
|
||||
}
|
||||
}
|
||||
@ -291,7 +319,7 @@ mod tests {
|
||||
)),
|
||||
..Default::default()
|
||||
};
|
||||
_ = theme_from_detector(options, &detector);
|
||||
_ = theme_impl(options, &detector);
|
||||
assert!(detector.was_called.get());
|
||||
}
|
||||
}
|
||||
@ -299,14 +327,14 @@ mod tests {
|
||||
#[test]
|
||||
fn called_for_auto_if_should_detect() {
|
||||
let detector = DetectorStub::should_detect(Some(Dark));
|
||||
_ = theme_from_detector(ThemeOptions::default(), &detector);
|
||||
_ = theme_impl(ThemeOptions::default(), &detector);
|
||||
assert!(detector.was_called.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_called_for_auto_if_not_should_detect() {
|
||||
let detector = DetectorStub::should_not_detect();
|
||||
_ = theme_from_detector(ThemeOptions::default(), &detector);
|
||||
_ = theme_impl(ThemeOptions::default(), &detector);
|
||||
assert!(!detector.was_called.get());
|
||||
}
|
||||
}
|
||||
@ -330,7 +358,7 @@ mod tests {
|
||||
},
|
||||
] {
|
||||
let detector = ConstantDetector(color_scheme);
|
||||
assert_eq!("Theme", theme_from_detector(options, &detector));
|
||||
assert_eq!("Theme", theme_impl(options, &detector).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -342,7 +370,7 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let detector = DetectorStub::should_detect(Some(Dark));
|
||||
_ = theme_from_detector(options, &detector);
|
||||
_ = theme_impl(options, &detector);
|
||||
assert!(!detector.was_called.get());
|
||||
}
|
||||
}
|
||||
@ -355,7 +383,7 @@ mod tests {
|
||||
let detector = ConstantDetector(None);
|
||||
assert_eq!(
|
||||
default_theme(ColorScheme::Dark),
|
||||
theme_from_detector(ThemeOptions::default(), &detector)
|
||||
theme_impl(ThemeOptions::default(), &detector).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
@ -371,7 +399,7 @@ mod tests {
|
||||
let detector = ConstantDetector(color_scheme);
|
||||
assert_eq!(
|
||||
default_theme(ColorScheme::Dark),
|
||||
theme_from_detector(options, &detector)
|
||||
theme_impl(options, &detector).to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -390,7 +418,7 @@ mod tests {
|
||||
let detector = ConstantDetector(Some(color_scheme));
|
||||
assert_eq!(
|
||||
default_theme(color_scheme),
|
||||
theme_from_detector(options, &detector)
|
||||
theme_impl(options, &detector).to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -409,7 +437,7 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let detector = ConstantDetector(color_scheme);
|
||||
assert_eq!("Dark", theme_from_detector(options, &detector));
|
||||
assert_eq!("Dark", theme_impl(options, &detector).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,7 +449,7 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let detector = ConstantDetector(Some(ColorScheme::Light));
|
||||
assert_eq!("Light", theme_from_detector(options, &detector));
|
||||
assert_eq!("Light", theme_impl(options, &detector).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user