Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for different themes based on Terminal color scheme #1746

Open
zachriggle opened this issue Jul 21, 2021 · 17 comments · May be fixed by #2896
Open

Support for different themes based on Terminal color scheme #1746

zachriggle opened this issue Jul 21, 2021 · 17 comments · May be fixed by #2896
Labels
feature-request New feature or request help wanted Extra attention is needed

Comments

@zachriggle
Copy link

zachriggle commented Jul 21, 2021

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.

@zachriggle zachriggle added the feature-request New feature or request label Jul 21, 2021
@sharkdp
Copy link
Owner

sharkdp commented Jul 25, 2021

Thank you for this request.

I didn't know that there are existing libraries/crates for this! And that it was even possible to detect it on xterm-compatible terminals. I think I'm in favor of implementing this. We should probably also add --theme-dark and --theme-light for users that prefer a config file over the environment variable solution.

By the way: did you see this section of the README? There are bat themes which can adapt to dark/light mode automatically.

@eth-p
Copy link
Collaborator

eth-p commented Oct 2, 2021

I did some research into terminal background detection a couple months ago for my own reasons, and I can actually explain how to do it without relying on any external library.

There are three ways to detect the background color:

OSC 11 Query: \ESC]11;?\a (ideal)

Sending this OS Command escape sequence to the terminal will cause it to reply with one of the following patterns:

  • \ESC]11;rgba:RRRR:GGGG:BBBB:AAAA
  • \ESC]11;rgb:RRRR:GGGG:BBBB
  • \ESC]11;rgb:RRR:GGG:BBB
  • \ESC]11;rgb:RR:GG:BB

Supported terminals (that I know of):

  • Terminal.app
  • iTerm 2
  • Alacritty
  • Hyper

Unsupported terminals (that I know of):

  • Tmux*
  • JediTerm (JetBrains IDEs)**

*You can work around this by telling tmux to forward the escape sequence to the terminal.
** Support was added to the JediTerm repo, but it hasn't made it into any of the IDEs yet.

How to use it:

Once you have the RGB values, you can use this formula to detect if the background is dark or light:

(Y = 0.2126*R + 0.7152*G + 0.0722*B) < 0.5 ? dark : light

Caveats

You need to wait for the terminal to reply through stdin, which can take anywhere between 1 and 100ms.

$COLORFGBG (has caveats)

Some terminals might set this this environment variable to contain the foreground or background color:

e.g. 0;7 for black text on a white background.

Caveats

  • Environment variables of running processes can't be changed.
  • Environment variable might not be forwarded over programs like ssh, telnet, docker run, kubectl exec, etc.
  • Requires having a table to check what color each ANSI color code corresponds to.

Guessing (last resort)

When all else fails, you can guess based on the terminal defaults. Since @zachriggle already came up with some good ways of determining the background color for each terminal from its configuration, here's my tips on how to detect which terminal is being used on MacOS:

# macOS Terminal
if [[ "${TERM_PROGRAM:-}" = "Apple_Terminal" ]]; then
  :
fi

# iTerm
if [[ "${TERM_PROGRAM:-}" = "iTerm.app" ]]; then
  :
fi

# Alacritty
if [[ "$__CFBundleIdentifier" = "io.alacritty" ]]; then
  :
fi

# Tmux
if [[ -n "$TMUX" ]]; then
  :
fi

@fsferrara
Copy link

Hi, this comment is not going to contribute to the discussion on this feature. I just wanted to share a workaround I am using on macos.

The ansi theme is able to correctly select an appropriate color since it uses the default one. Just set the BAT_THEME=ansi env variable to select the ansi theme.

@jasikpark
Copy link

I'd love to see this happen, especially since the macOS Terminal workaround does not extend to the VSCode terminal:

image

api on  main via 🐹 v1.17.5 
❯ defaults read -globalDomain AppleInterfaceStyle
Dark

I get "Dark" when my terminal is in light mode

I'll just use ansi as my theme for now, but It'd be great to have it work.

Also thank you for making such an amazing tool!!!!

@jasikpark
Copy link

+1 also to simply adding the ansi theme as the default / updating the default theme to support autodetecting light/dark mode as an incremental fix

@Enselic Enselic added the help wanted Extra attention is needed label Mar 11, 2022
@Enselic
Copy link
Collaborator

Enselic commented Mar 11, 2022

I'm labeling as "help wanted", because fixing this would substantially improve the user experience for users (especially new ones) that use a light terminal background.

@beauwilliams
Copy link

beauwilliams commented Apr 7, 2022

On mac OSX, I'm using this script that I place in my zshrc. However the caveat is that you need to reload your shell for the changes to be applied.

I am trying to figure our how to update $BAT_THEME when the system theme changes. I have my neovim, iTerm and everything else change theme when I switch from light to dark and vice versa. However bat is the last remaining one I can't yet figure out how to update automatically.

This means if I am working on shell and OS theme changes, bat will fall out of sync, until I start a new shell.

This also works with VSCode. But again, you need to open a new terminal prompt for changes to show.

if [ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" = "Dark" ];
  then export BAT_THEME='gruvbox-dark'
  else export BAT_THEME='gruvbox-light'
fi;

@beauwilliams
Copy link

beauwilliams commented Apr 7, 2022

Updates. I found a fix for my problem.

update_theme() (
if [ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" = "Dark" ];
  then export BAT_THEME='gruvbox-dark'
  else export BAT_THEME='gruvbox-light'
)
precmd() { update_theme }

This way on each prompt, theme is updated. E.g run nvim to begin editing a file, it will automatically update BAT depending on system theme. Not sure how performant this is. But it solves my problem.

This code can be placed in your zshrc. For bash users, $PROMPT_COMMAND can be used instead, works very much the same.

@Enselic Enselic pinned this issue Apr 26, 2022
@Enselic Enselic changed the title Feature: Allow multiple themes, depending on Terminal color scheme Support for different themes based on Terminal color scheme Apr 26, 2022
@Enselic
Copy link
Collaborator

Enselic commented May 21, 2022

Here is a PR to improve the situation on macOS: #2197. Any feedback is very welcomed, including objections to the approach as a whole.

@sharkdp
Copy link
Owner

sharkdp commented Jul 11, 2022

This crate (https://github.com/dalance/termbg) was suggested on the fd repo for a similar problem. Might be worth looking into.

Amorymeltzer added a commit to Amorymeltzer/dotfiles that referenced this issue Jan 5, 2023
The comment summarizes it pretty well, but basically:

- bat (as of 0.22 <sharkdp/bat#2197>) defaults to switching theme based on macOS' light/dark mode
- That isn't the same thing as whether the terminal theme is light or dark
- There are ways to do that, both in Apple's terminal and in iTerm2 (in the 2.5 beta), but until then/if it's not desired, this works
- One day there will [hopefully](sharkdp/bat#1746) be a way to set both light and dark themes for `bat`
- None of this makes much of any difference for `delta`
@kad
Copy link

kad commented Mar 27, 2023

Here is a PR to improve the situation on macOS: #2197. Any feedback is very welcomed, including objections to the approach as a whole.

it would be good if that logic would apply only to native terminal app in macos. for iTerm it doesn't really make sense.

@will
Copy link

will commented Mar 27, 2023

I use iTerm2 and I would love to have bat automatically detect the background.

I have iterm switch colorscheems between light and dark corresponding with the overall OS switching with different times of day, and when its light, bat is really illegible by default. I've gone and made manual changes so that it works for me for both light and dark, but doing that was tedious.

@m-ou-se
Copy link

m-ou-se commented May 14, 2023

Here is a PR to improve the situation on macOS: #2197. Any feedback is very welcomed, including objections to the approach as a whole.

it would be good if that logic would apply only to native terminal app in macos. for iTerm it doesn't really make sense.

I ran into this issue using iTerm with a dark background on (light-themed) macos. The text was nearly unreadable.

Related to this, it also does not make sense to check the theme locally on macos when I'm running bat through ssh from another computer.

macedigital added a commit to macedigital/dotfiles that referenced this issue Jul 22, 2023
Using `ansi` theme by default as it has can adjust to dark or light base
color scheme transparently, without having to script anything.

Context sharkdp/bat#1746.
aykevl added a commit to aykevl/bat that referenced this issue Jul 28, 2023
This uses the termbg crate, as discussed here:
sharkdp#1746
Other than that, it uses the same logic as on macOS.

This behavior should probably be extended to macOS too, for cases like a
terminal with a light background while the system theme is dark (or vice
versa). But I didn't want to accidentally break anything on macOS.
@aykevl
Copy link

aykevl commented Nov 23, 2023

I made a PR to fix this on Linux/Windows using termbg: #2631
Unfortunately, it requires a change to termbg to pass tests.

aykevl added a commit to aykevl/bat that referenced this issue Dec 7, 2023
This uses the termbg crate, as discussed here:
sharkdp#1746
Other than that, it uses the same logic as on macOS.

This behavior should probably be extended to macOS too, for cases like a
terminal with a light background while the system theme is dark (or vice
versa). But I didn't want to accidentally break anything on macOS.
aykevl added a commit to aykevl/bat that referenced this issue Dec 8, 2023
This uses the termbg crate, as discussed here:
sharkdp#1746
Other than that, it uses the same logic as on macOS.

This behavior should probably be extended to macOS too, for cases like a
terminal with a light background while the system theme is dark (or vice
versa). But I didn't want to accidentally break anything on macOS.
@sharkdp sharkdp unpinned this issue Feb 7, 2024
@bash bash linked a pull request Mar 15, 2024 that will close this issue
1 task
@sarmong
Copy link

sarmong commented May 10, 2024

I don't know how exactly highlighting works in bat, but why doesn't it take terminal colorset into account?

For example, when changing from dark theme to a light one in kitty, I update values of color0 to color15, then, in zsh I color certain parts of my prompt with color 0 or 15 and it updates automatically when I reload my kitty config, because in dark theme color15 is light and in light theme it is dark.

When I open man page without bat, it picks up the colors of my terminal in the same way.

@aykevl
Copy link

aykevl commented May 21, 2024

I don't know how exactly highlighting works in bat, but why doesn't it take terminal colorset into account?

You can use --theme=ansi for this (and this is what I use). There aren't as many colors available, but it will switch between them when the terminal switches the color profile.
This even happens in the shell history, which wouldn't happen when emitting specific colors as with --theme=GitHub for example.

@j-lakeman
Copy link

FYI Solarized (dark) works quite well for me on light and dark themed terminals both on macOS and Linux. I prefer it over ansi as it offers more colours e.g. for displaying man pages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.