Skip to content

Support for different themes based on Terminal color scheme #1746

@zachriggle

Description

@zachriggle

Thanks!

As always and once again, thank you @sharkdp for your time and effort. Hopefully I'm not being a nuisance with these requests.

Homework

This is not a direct duplicate of #641 or #689, but it is related. I think that bat can be improved beyond its current suggestion of manually running defaults read by performing actual detection of background colors, and exposing multiple theme options.

Current State

Currently, bat allows specification of a SINGLE theme, via either $BAT_THEME or --theme. This is great, and fits most use-cases. The README.md proposes a way to work around this, which is macOS Terminal.app-specific, and a little hacky. I think bat can do even better (and work on more platforms than macOS)!

When distributing tools built upon bat, it's not generally predictable what background color / terminal theme a user has configured for {Terminal.app,iTerm2.app,Konsole,gnome-terminal,etc}.

Additionally, the default theme (i.e. no --theme nor $BAT_THEME) may not be appropriate for a given terminal (and the work-around does not fix this).

Finally, the theme may actually need to change, if the user has multiple different terminal profiles, or if the color scheme of the profile changes based on external factors (such as the default Terminal.app theme, which may change based on the current time of day).

Feature

It would be nice to have bat attempt to do some forms of auto-detection of the terminal's default background color, and allow the user to supply $BAT_THEME_DARK and $BAT_THEME_LIGHT, one of which is auto-selected by bat.

These should probably have lower precedence of $BAT_THEME and --theme.

There are several reasons for wanting this:

  • Users who use macOS's "Auto" appearance switches between "Light" and "Dark" at various times of day, so a single theme isn't sufficient.
  • Projects which build upon bat may want to provide modified themes (and cache, and .bin files) to work around features that bat doesn't have yet (e.g. Feature: Allow color theme overrides on the command line #1745) and forcefully override e.g. $BAT_THEME with their modified theme. These tools may wish to provide two themes -- one for light mode, and one for dark mode.

Libraries and Other Prior Work

There are several projects that may be directly usable, or usable for inspiration and mechanism, for auto-detecting a light or dark terminal.

termbg

Rust library that claims to do exactly what's needed.

However, it has at least one bug on macOS for Terminal.app: dalance/termbg#8

https://github.com/dalance/termbg

rust-dark-light

Rust project, not sure how well it works or what systems are supported.

https://github.com/frewsxcv/rust-dark-light

termenv

While written in Go, this is a whole library for manipulating colors, and also features UNIX- and Windows-compatible light/dark mode detection.

In particular, it has one function that does what we need:

// Returns whether terminal uses a dark-ish background
darkTheme := termenv.HasDarkBackground()

https://github.com/muesli/termenv

Implementation Details - SKIP THIS SECTION, SEE ABOVE

Update: I did some more searching, and found several Rust (and other) libraries which may be usable to achieve this. termbg is very promising, and worked in all terminals I tested via cargo run. This section can be ignored.

Realistically, background color detection of the "current terminal" could likely be its own tool / library.

Detection of whether the foreground is a light or dark color is in itself a challenge. I've proposed some possibilities below, but I expect that the mechanism used by git-delta is likely sound.

Separately, determining whether a specific color is "light" or "dark" is pretty difficult, but I expect that checking that all three of R, G, and B are below some threshold (say, 0x60) you could say that it's a "dark" theme and anything else (e.g. #00ff00) would be a "light" theme.

Linux

xterm

A little bit of Googling around shows that Xterm, at least (and possibly other terminals) respond to certain escape sequences with their color configurations: https://unix.stackexchange.com/a/172674

This may be more generically applicable than just xterm itself, which is why I listed this first.

gnome-terminal

I can't speak to this, but it's a starting point. Copied from https://unix.stackexchange.com/a/133920:

$ dconf list /org/gnome/terminal/legacy/profiles:/
<profile id>

$ dconf read /org/gnome/terminal/legacy/profiles:/<profile id>/background-color
'rgb(0,0,0)'

macOS

A generic way to check for whether the theme should be light or dark is to check defaults read -globalDomain AppleInterfaceStyle (~10ms), which either emits nothing (for Light mode) or "Dark". Note that if the user configures "Auto", the output of this value will reflect the current setting.

iTerm2

This terminal can be detected via the environment, and will have TERM_PROGRAM=iTerm.app.

For iTerm2, it should be possible to determine the foreground color from its configuration plist and the $ITERM_PROFILE environment variable.

For example, in Python one can do:

#!/usr/bin/env python3
import plistlib
import os

plist = plistlib.load(open('Library/Preferences/com.googlecode.iterm2.plist', 'rb'))
profile = os.getenv('ITERM_PROFILE', 'Default')

def hexify(color):
    r = int(color['Red Component'] * 255) << 16
    g = int(color['Green Component'] * 255) << 8
    b = int(color['Blue Component'] * 255)
    return '#%06x' % (r | g | b)

for b in plist['New Bookmarks']:
    if b['Name'] != profile:
        continue
    print(f"Foreground: {hexify(b['Foreground Color'])}")
    print(f"Background: {hexify(b['Background Color'])}")
    break

Which, for me, with Solarized Dark as my theme, prints out:

Foreground: #839496
Background: #002b36

Terminal.app

This terminal can be detected via the environment, and will have TERM_PROGRAM=Apple_Terminal.

Terminal.app is more complicated, because it uses NSKeyedArchiver and serialized NSColor objects.

However, for the built-in Profiles (which I expect most users to use one of), we can pretty easily classify them with a simple shell script. Note that the script takes ~160ms to run, despite being a single line, so it might make sense to cache it for some short period of time (e.g. 1 minute).

#!/usr/bin/env osascript
tell application "Terminal" to return name of current settings of first window

This will cause a TCC prompt if it's run from another application, e.g. iTerm2.app, but that should realistically never happen for color detection. The output is the current Profile name, e.g. "Basic", "Grass", "Homebrew", and so on. Since there's only 10 themes, it should be easy to just have a mapping for each one.

It is important to note that the actual colors of the "Basic" profile for Terminal.app is affected by AppleInterfaceStyle, as mentioned above (background may be black OR white depending on the setting). I have not tested other Terminal.app themes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions