summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTau <git@tau.garden>2024-03-12 16:05:44 +0100
committerGitHub <noreply@github.com>2024-03-12 11:05:44 -0400
commit2f76c56d91d3d49feb170b89d7526e0272634998 (patch)
treea1f2ad71790e45527ec8c53c9169bff3634074a3
parent9b775ecd36bf6b399186219a0b9345ccd403d6ef (diff)
Detect Dark/Light Mode from Terminal (#1615)
-rw-r--r--Cargo.lock39
-rw-r--r--Cargo.toml1
-rw-r--r--src/cli.rs36
-rw-r--r--src/features/side_by_side.rs2
-rw-r--r--src/options/set.rs1
-rw-r--r--src/options/theme.rs76
6 files changed, 148 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d4344afa..de5486e9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -581,6 +581,7 @@ dependencies = [
"smol_str",
"syntect",
"sysinfo",
+ "terminal-colorsaurus",
"unicode-segmentation",
"unicode-width",
"xdg",
@@ -753,9 +754,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.151"
+version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libgit2-sys"
@@ -835,6 +836,17 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1296,6 +1308,29 @@ dependencies = [
]
[[package]]
+name = "terminal-colorsaurus"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c374383f597b763eb3bd06bc4e18f85510a52d7f1ac762f0c7e413ce696079fc"
+dependencies = [
+ "libc",
+ "memchr",
+ "mio",
+ "terminal-trx",
+ "thiserror",
+]
+
+[[package]]
+name = "terminal-trx"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a4af7c93f02d5bd5e120c812f7fb413003b7060e8a22d0ea90346f1be769210"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 0a714f0e..8d0917be 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,6 +54,7 @@ unicode-segmentation = "1.10.1"
unicode-width = "0.1.10"
xdg = "2.4.1"
clap_complete = "4.4.4"
+terminal-colorsaurus = "0.3.1"
[dependencies.git2]
version = "0.18.2"
diff --git a/src/cli.rs b/src/cli.rs
index d098e736..4cddd6fb 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -3,7 +3,7 @@ use std::ffi::OsString;
use std::path::{Path, PathBuf};
use bat::assets::HighlightingAssets;
-use clap::{ColorChoice, CommandFactory, FromArgMatches, Parser, ValueHint};
+use clap::{ColorChoice, CommandFactory, FromArgMatches, Parser, ValueEnum, ValueHint};
use clap_complete::Shell;
use lazy_static::lazy_static;
use syntect::highlighting::Theme as SyntaxTheme;
@@ -294,6 +294,29 @@ pub struct Opt {
/// set this in per-repository git config (.git/config)
pub default_language: Option<String>,
+ /// Detect whether or not the terminal is dark or light by querying for its colors.
+ ///
+ /// Ignored if either `--dark` or `--light` is specified.
+ ///
+ /// Querying the terminal for its colors requires "exclusive" access
+ /// since delta reads/writes from the terminal and enables/disables raw mode.
+ /// This causes race conditions with pagers such as less when they are attached to the
+ /// same terminal as delta.
+ ///
+ /// This is usually only an issue when the output is manually piped to a pager.
+ /// For example: `git diff | delta | less`.
+ /// Otherwise, if delta starts the pager itself, then there's no race condition
+ /// since the pager is started *after* the color is detected.
+ ///
+ /// `auto` tries to account for these situations by testing if the output is redirected.
+ ///
+ /// The `--color-only` option is treated as an indicator that delta is used
+ /// as `interactive.diffFilter`. In this case the color is queried from the terminal even
+ /// though the output is redirected.
+ ///
+ #[arg(long = "detect-dark-light", value_enum, default_value_t = DetectDarkLight::default())]
+ pub detect_dark_light: DetectDarkLight,
+
#[arg(long = "diff-highlight")]
/// Emulate diff-highlight.
///
@@ -1124,6 +1147,17 @@ pub enum InspectRawLines {
False,
}
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum)]
+pub enum DetectDarkLight {
+ /// Only query the terminal for its colors if the output is not redirected.
+ #[default]
+ Auto,
+ /// Always query the terminal for its colors.
+ Always,
+ /// Never query the terminal for its colors.
+ Never,
+}
+
impl Opt {
pub fn from_args_and_git_config(
env: DeltaEnv,
diff --git a/src/features/side_by_side.rs b/src/features/side_by_side.rs
index 82d66593..a8182359 100644
--- a/src/features/side_by_side.rs
+++ b/src/features/side_by_side.rs
@@ -592,6 +592,7 @@ pub mod ansifill {
pub mod tests {
use crate::ansi::strip_ansi_codes;
use crate::features::line_numbers::tests::*;
+ use crate::options::theme;
use crate::tests::integration_test_utils::{make_config_from_args, run_delta, DeltaTest};
#[test]
@@ -642,6 +643,7 @@ pub mod tests {
#[test]
fn test_two_plus_lines_spaces_and_ansi() {
+ let _override = theme::test_utils::DetectLightModeOverride::new(false);
DeltaTest::with_args(&[
"--side-by-side",
"--width",
diff --git a/src/options/set.rs b/src/options/set.rs
index 262096c5..cfb42987 100644
--- a/src/options/set.rs
+++ b/src/options/set.rs
@@ -43,6 +43,7 @@ macro_rules! set_options {
"24-bit-color",
"diff-highlight", // Does not exist as a flag on config
"diff-so-fancy", // Does not exist as a flag on config
+ "detect-dark-light", // Does not exist as a flag on config
"features", // Processed differently
// Set prior to the rest
"no-gitconfig",
diff --git a/src/options/theme.rs b/src/options/theme.rs
index cb7b9b04..f53a0821 100644
--- a/src/options/theme.rs
+++ b/src/options/theme.rs
@@ -1,3 +1,5 @@
+use std::io::{stdout, IsTerminal};
+
/// 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
@@ -9,7 +11,7 @@
use bat;
use bat::assets::HighlightingAssets;
-use crate::cli;
+use crate::cli::{self, DetectDarkLight};
#[allow(non_snake_case)]
pub fn set__is_light_mode__syntax_theme__syntax_set(
@@ -20,7 +22,7 @@ pub fn set__is_light_mode__syntax_theme__syntax_set(
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,
+ get_is_light(opt),
);
opt.computed.is_light_mode = is_light_mode;
@@ -84,9 +86,9 @@ fn is_no_syntax_highlighting_syntax_theme_name(theme_name: &str) -> bool {
fn get_is_light_mode_and_syntax_theme_name(
theme_arg: Option<&String>,
bat_theme_env_var: Option<&String>,
- light_mode_arg: bool,
+ light_mode: bool,
) -> (bool, String) {
- match (theme_arg, bat_theme_env_var, light_mode_arg) {
+ match (theme_arg, bat_theme_env_var, light_mode) {
(None, None, false) => (false, DEFAULT_DARK_SYNTAX_THEME.to_string()),
(Some(theme_name), _, false) => (is_light_syntax_theme(theme_name), theme_name.to_string()),
(None, Some(theme_name), false) => {
@@ -98,8 +100,72 @@ fn get_is_light_mode_and_syntax_theme_name(
}
}
+fn get_is_light(opt: &cli::Opt) -> bool {
+ get_is_light_opt(opt)
+ .or_else(|| should_detect_dark_light(opt).then(detect_light_mode))
+ .unwrap_or_default()
+}
+
+fn get_is_light_opt(opt: &cli::Opt) -> Option<bool> {
+ if opt.light {
+ Some(true)
+ } else if opt.dark {
+ Some(false)
+ } else {
+ None
+ }
+}
+
+/// See [`cli::Opt::detect_dark_light`] for a detailed explanation.
+fn should_detect_dark_light(opt: &cli::Opt) -> bool {
+ match opt.detect_dark_light {
+ DetectDarkLight::Auto => opt.color_only || stdout().is_terminal(),
+ DetectDarkLight::Always => true,
+ DetectDarkLight::Never => false,
+ }
+}
+
+fn detect_light_mode() -> bool {
+ use terminal_colorsaurus::{color_scheme, QueryOptions};
+
+ #[cfg(test)]
+ if let Some(value) = test_utils::DETECT_LIGHT_MODE_OVERRIDE.get() {
+ return value;
+ }
+
+ color_scheme(QueryOptions::default())
+ .map(|c| c.is_dark_on_light())
+ .unwrap_or_default()
+}
+
+#[cfg(test)]
+pub(crate) mod test_utils {
+ thread_local! {
+ pub(super) static DETECT_LIGHT_MODE_OVERRIDE: std::cell::Cell<Option<bool>> = std::cell::Cell::new(None);
+ }
+
+ pub(crate) struct DetectLightModeOverride {
+ old_value: Option<bool>,
+ }
+
+ impl DetectLightModeOverride {
+ pub(crate) fn new(value: bool) -> Self {
+ let old_value = DETECT_LIGHT_MODE_OVERRIDE.get();
+ DETECT_LIGHT_MODE_OVERRIDE.set(Some(value));
+ DetectLightModeOverride { old_value }
+ }
+ }
+
+ impl Drop for DetectLightModeOverride {
+ fn drop(&mut self) {
+ DETECT_LIGHT_MODE_OVERRIDE.set(self.old_value)
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
+ use super::test_utils::DetectLightModeOverride;
use super::*;
use crate::color;
use crate::tests::integration_test_utils;
@@ -107,6 +173,8 @@ mod tests {
// TODO: Test influence of BAT_THEME env var. E.g. see utils::process::tests::FakeParentArgs.
#[test]
fn test_syntax_theme_selection() {
+ let _override = DetectLightModeOverride::new(false);
+
#[derive(PartialEq)]
enum Mode {
Light,