mirror of
https://github.com/sharkdp/bat.git
synced 2025-04-18 16:50:33 +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,
|
Some("auto") => StripAnsiMode::Auto,
|
||||||
_ => unreachable!("other values for --strip-ansi are not allowed"),
|
_ => 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()
|
visible_lines: match self.matches.try_contains_id("diff").unwrap_or_default()
|
||||||
&& self.matches.get_flag("diff")
|
&& 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.
|
//! Utilities for choosing an appropriate theme for syntax highlighting.
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::fmt;
|
||||||
use std::io::IsTerminal as _;
|
use std::io::IsTerminal as _;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Chooses an appropriate theme or falls back to a default theme
|
/// Chooses an appropriate theme or falls back to a default theme
|
||||||
/// based on the user-provided options and the color scheme of the terminal.
|
/// 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.
|
/// 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.
|
/// Options for configuring the theme used for syntax highlighting.
|
||||||
/// Used together with [`theme`].
|
/// Used together with [`theme`].
|
||||||
#[derive(Debug, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
pub struct ThemeOptions {
|
pub struct ThemeOptions {
|
||||||
/// Always use this theme regardless of the terminal's background color.
|
/// Always use this theme regardless of the terminal's background color.
|
||||||
/// This corresponds with the `BAT_THEME` environment variable and the `--theme` option.
|
/// This corresponds with the `BAT_THEME` environment variable and the `--theme` option.
|
||||||
@ -40,7 +45,14 @@ pub struct ThemeOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// What theme should `bat` use?
|
/// 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 {
|
pub enum ThemePreference {
|
||||||
/// Choose between [`ThemeOptions::theme_dark`] and [`ThemeOptions::theme_light`]
|
/// Choose between [`ThemeOptions::theme_dark`] and [`ThemeOptions::theme_light`]
|
||||||
/// based on the terminal's (or the OS') color scheme.
|
/// 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::Default, ThemeName::from_str("default").unwrap());
|
||||||
/// assert_eq!(ThemeName::Named("example".to_string()), ThemeName::from_str("example").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 {
|
pub enum ThemeName {
|
||||||
Named(String),
|
Named(String),
|
||||||
Default,
|
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.
|
/// How to choose between dark and light.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum ColorSchemePreference {
|
pub enum ColorSchemePreference {
|
||||||
@ -142,6 +145,26 @@ pub enum ColorScheme {
|
|||||||
Light,
|
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(
|
fn color_scheme_impl(
|
||||||
pref: ColorSchemePreference,
|
pref: ColorSchemePreference,
|
||||||
detector: &dyn ColorSchemeDetector,
|
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.
|
// 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.
|
||||||
match options.theme {
|
match options.theme {
|
||||||
ThemePreference::Fixed(theme_name) => theme_name.into_theme(ColorScheme::default()),
|
ThemePreference::Fixed(theme) => ThemeResult {
|
||||||
ThemePreference::Auto(color_scheme_preference) => {
|
theme,
|
||||||
let color_scheme = color_scheme_impl(color_scheme_preference, detector);
|
color_scheme: None,
|
||||||
choose_theme(options, color_scheme)
|
},
|
||||||
.map(|t| t.into_theme(color_scheme))
|
ThemePreference::Auto(pref) => {
|
||||||
.unwrap_or_else(|| default_theme(color_scheme).to_owned())
|
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),
|
theme: ThemePreference::Auto(pref),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
_ = theme_from_detector(options, &detector);
|
_ = theme_impl(options, &detector);
|
||||||
assert!(!detector.was_called.get());
|
assert!(!detector.was_called.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,7 +319,7 @@ mod tests {
|
|||||||
)),
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
_ = theme_from_detector(options, &detector);
|
_ = theme_impl(options, &detector);
|
||||||
assert!(detector.was_called.get());
|
assert!(detector.was_called.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,14 +327,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn called_for_auto_if_should_detect() {
|
fn called_for_auto_if_should_detect() {
|
||||||
let detector = DetectorStub::should_detect(Some(Dark));
|
let detector = DetectorStub::should_detect(Some(Dark));
|
||||||
_ = theme_from_detector(ThemeOptions::default(), &detector);
|
_ = theme_impl(ThemeOptions::default(), &detector);
|
||||||
assert!(detector.was_called.get());
|
assert!(detector.was_called.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn not_called_for_auto_if_not_should_detect() {
|
fn not_called_for_auto_if_not_should_detect() {
|
||||||
let detector = DetectorStub::should_not_detect();
|
let detector = DetectorStub::should_not_detect();
|
||||||
_ = theme_from_detector(ThemeOptions::default(), &detector);
|
_ = theme_impl(ThemeOptions::default(), &detector);
|
||||||
assert!(!detector.was_called.get());
|
assert!(!detector.was_called.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,7 +358,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
] {
|
] {
|
||||||
let detector = ConstantDetector(color_scheme);
|
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()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let detector = DetectorStub::should_detect(Some(Dark));
|
let detector = DetectorStub::should_detect(Some(Dark));
|
||||||
_ = theme_from_detector(options, &detector);
|
_ = theme_impl(options, &detector);
|
||||||
assert!(!detector.was_called.get());
|
assert!(!detector.was_called.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,7 +383,7 @@ mod tests {
|
|||||||
let detector = ConstantDetector(None);
|
let detector = ConstantDetector(None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_theme(ColorScheme::Dark),
|
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);
|
let detector = ConstantDetector(color_scheme);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_theme(ColorScheme::Dark),
|
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));
|
let detector = ConstantDetector(Some(color_scheme));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
default_theme(color_scheme),
|
default_theme(color_scheme),
|
||||||
theme_from_detector(options, &detector)
|
theme_impl(options, &detector).to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -409,7 +437,7 @@ mod tests {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let detector = ConstantDetector(color_scheme);
|
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()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let detector = ConstantDetector(Some(ColorScheme::Light));
|
let detector = ConstantDetector(Some(ColorScheme::Light));
|
||||||
assert_eq!("Light", theme_from_detector(options, &detector));
|
assert_eq!("Light", theme_impl(options, &detector).to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user