/// Delta doesn't have a formal concept of a "theme". What it has is /// (a) the choice of syntax-highlighting theme /// (b) the choice of light-background-mode vs dark-background-mode, which determine certain /// default color choices /// This module sets those options. If the light/dark background mode choice is not made explicitly /// by the user, it is determined by the classification of the syntax theme into light-background /// vs dark-background syntax themes. If the user didn't choose a syntax theme, a dark-background /// default is selected. use syntect::highlighting::ThemeSet; use crate::bat_utils::assets::HighlightingAssets; use crate::cli; use crate::env; #[allow(non_snake_case)] pub fn set__is_light_mode__syntax_theme__syntax_set( opt: &mut cli::Opt, assets: HighlightingAssets, ) { let syntax_theme_name_from_bat_theme = env::get_env_var("BAT_THEME"); let (is_light_mode, syntax_theme_name) = get_is_light_mode_and_syntax_theme_name( opt.syntax_theme.as_ref(), syntax_theme_name_from_bat_theme.as_ref(), opt.light, &assets.theme_set, ); opt.computed.is_light_mode = is_light_mode; opt.computed.syntax_theme = if is_no_syntax_highlighting_syntax_theme_name(&syntax_theme_name) { None } else { Some(assets.theme_set.themes[&syntax_theme_name].clone()) }; opt.computed.syntax_set = assets.syntax_set; } pub fn is_light_syntax_theme(theme: &str) -> bool { LIGHT_SYNTAX_THEMES.contains(&theme) || theme.to_lowercase().contains("light") } const LIGHT_SYNTAX_THEMES: [&str; 6] = [ "GitHub", "gruvbox-light", "gruvbox-white", "Monokai Extended Light", "OneHalfLight", "Solarized (light)", ]; const DEFAULT_LIGHT_SYNTAX_THEME: &str = "GitHub"; const DEFAULT_DARK_SYNTAX_THEME: &str = "Monokai Extended"; fn is_no_syntax_highlighting_syntax_theme_name(theme_name: &str) -> bool { theme_name.to_lowercase() == "none" } /// Return a (theme_name, is_light_mode) tuple. /// theme_name == None in return value means syntax highlighting is disabled. /// /// There are two types of color choices that have to be made: /// 1. The choice of "theme". This is the language syntax highlighting theme; you have to make this /// choice when using `bat` also. /// 2. The choice of "light vs dark mode". This determines whether the background colors should be /// chosen for a light or dark terminal background. (`bat` has no equivalent.) /// /// Basically: /// 1. The theme is specified by the `--syntax-theme` option. If this isn't supplied then it is specified /// by the `BAT_THEME` environment variable. /// 2. Light vs dark mode is specified by the `--light` or `--dark` options. If these aren't /// supplied then it is inferred from the chosen theme. /// /// In the absence of other factors, the default assumes a dark terminal background. /// /// Specifically, the rules are as follows: /// /// | --theme | $BAT_THEME | --light/--dark | Behavior | /// |------------|------------|----------------|----------------------------------------------------------------------------| /// | - | - | - | default dark theme, dark mode | /// | some_theme | (IGNORED) | - | some_theme with light/dark mode inferred accordingly | /// | - | BAT_THEME | - | BAT_THEME, with light/dark mode inferred accordingly | /// | - | - | yes | default light/dark theme, light/dark mode | /// | some_theme | (IGNORED) | yes | some_theme, light/dark mode (even if some_theme conflicts with light/dark) | /// | - | BAT_THEME | yes | BAT_THEME, light/dark mode (even if BAT_THEME conflicts with light/dark) | fn get_is_light_mode_and_syntax_theme_name( theme_arg: Option<&String>, bat_theme_env_var: Option<&String>, light_mode_arg: bool, theme_set: &ThemeSet, ) -> (bool, String) { let theme_arg = valid_syntax_theme_name_or_none(theme_arg, theme_set); let bat_theme_env_var = valid_syntax_theme_name_or_none(bat_theme_env_var, theme_set); match (theme_arg, bat_theme_env_var, light_mode_arg) { (None, None, false) => (false, DEFAULT_DARK_SYNTAX_THEME.to_string()), (Some(theme_name), _, false) => (is_light_syntax_theme(&theme_name), theme_name), (None, Some(theme_name), false) => (is_light_syntax_theme(&theme_name), theme_name), (None, None, true) => (true, DEFAULT_LIGHT_SYNTAX_THEME.to_string()), (Some(theme_name), _, is_light_mode) => (is_light_mode, theme_name), (None, Some(theme_name), is_light_mode) => (is_light_mode, theme_name), } } // At this stage the theme name is considered valid if it is either a real theme name or the special // no-syntax-highlighting name. fn valid_syntax_theme_name_or_none( theme_name: Option<&String>, theme_set: &ThemeSet, ) -> Option { match theme_name { Some(name) if is_no_syntax_highlighting_syntax_theme_name(name) || theme_set.themes.contains_key(name) => { Some(name.to_string()) } _ => None, } } #[cfg(test)] mod tests { use std::env; use super::*; use crate::color; use crate::tests::integration_test_utils; #[test] fn test_syntax_theme_selection() { #[derive(PartialEq)] enum Mode { Light, Dark, } for ( syntax_theme, bat_theme_env_var, mode, // (--light, --dark) expected_syntax_theme, expected_mode, ) in vec![ (None, "", None, DEFAULT_DARK_SYNTAX_THEME, Mode::Dark), (Some("GitHub"), "", None, "GitHub", Mode::Light), (Some("GitHub"), "1337", None, "GitHub", Mode::Light), (None, "1337", None, "1337", Mode::Dark), ( None, "", None, DEFAULT_DARK_SYNTAX_THEME, Mode::Dark, ), ( None, "", Some(Mode::Light), DEFAULT_LIGHT_SYNTAX_THEME, Mode::Light, ), ( None, "", Some(Mode::Dark), DEFAULT_DARK_SYNTAX_THEME, Mode::Dark, ), ( None, "<@@@@@>", Some(Mode::Light), DEFAULT_LIGHT_SYNTAX_THEME, Mode::Light, ), (None, "GitHub", Some(Mode::Light), "GitHub", Mode::Light), (Some("none"), "", None, "none", Mode::Dark), (Some("None"), "", Some(Mode::Light), "none", Mode::Light), ] { if bat_theme_env_var == "" { env::remove_var("BAT_THEME") } else { env::set_var("BAT_THEME", bat_theme_env_var); } let mut args = vec![]; if let Some(syntax_theme) = syntax_theme { args.push("--syntax-theme"); args.push(syntax_theme); } let is_true_color = true; if is_true_color { args.push("--true-color"); args.push("always"); } else { args.push("--true-color"); args.push("never"); } match mode { Some(Mode::Light) => { args.push("--light"); } Some(Mode::Dark) => { args.push("--dark"); } None => {} } let config = integration_test_utils::make_config_from_args(&args); assert_eq!( &config .syntax_theme .clone() .map(|t| t.name.unwrap()) .unwrap_or("none".to_string()), expected_syntax_theme ); if is_no_syntax_highlighting_syntax_theme_name(expected_syntax_theme) { assert!(config.syntax_theme.is_none()) } else { assert_eq!( config.syntax_theme.unwrap().name.as_ref().unwrap(), expected_syntax_theme ); } assert_eq!( config.minus_style.ansi_term_style.background.unwrap(), color::get_minus_background_color_default( expected_mode == Mode::Light, is_true_color ) ); assert_eq!( config.minus_emph_style.ansi_term_style.background.unwrap(), color::get_minus_emph_background_color_default( expected_mode == Mode::Light, is_true_color ) ); assert_eq!( config.plus_style.ansi_term_style.background.unwrap(), color::get_plus_background_color_default( expected_mode == Mode::Light, is_true_color ) ); assert_eq!( config.plus_emph_style.ansi_term_style.background.unwrap(), color::get_plus_emph_background_color_default( expected_mode == Mode::Light, is_true_color ) ); } } }