mirror of
https://github.com/ViViDboarder/shoestrap.git
synced 2024-09-19 22:17:13 +00:00
394 lines
12 KiB
Python
Executable File
394 lines
12 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
"""Derive colors determines colorschemes for various tools based on the
|
|
current terminal"""
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
from subprocess import PIPE, Popen, check_output
|
|
from typing import Optional, Tuple
|
|
|
|
TERM_VAR = "TERM_PROFILE"
|
|
VIM_VAR = "VIM_COLOR"
|
|
NVIM_VAR = "NVIM_COLOR"
|
|
BAT_VAR = "BAT_THEME"
|
|
FISH_VAR = "FISH_THEME"
|
|
TERMINAL_SETTINGS_SCRIPT = """
|
|
set current_tty to "{}"
|
|
tell application "Terminal"
|
|
repeat with win in windows
|
|
repeat with the_tab in tabs of win
|
|
set tab_settings to the current settings of the_tab
|
|
if current_tty is equal to the tty of the_tab then
|
|
return the name of tab_settings
|
|
end if
|
|
end repeat
|
|
end repeat
|
|
end tell
|
|
"""
|
|
|
|
|
|
class TermProfileError(ValueError):
|
|
"""Error encapsulating inability to identify the terminal profile"""
|
|
pass
|
|
|
|
|
|
def warn(message: str):
|
|
"""Print a warning message to stderr"""
|
|
print(f"derive_colors: Warning: {message}", file=sys.stderr)
|
|
|
|
|
|
def run_applescript(script: str) -> Tuple[int, str, str]:
|
|
"""Executes an applescript string and return the results"""
|
|
with Popen(
|
|
['osascript', '-'],
|
|
stdin=PIPE,
|
|
stdout=PIPE,
|
|
stderr=PIPE,
|
|
universal_newlines=True,
|
|
) as proc:
|
|
stdout, stderr = proc.communicate(script)
|
|
return proc.returncode, str(stdout).strip(), str(stderr).strip()
|
|
|
|
|
|
def get_terminal_profile(force: bool = False):
|
|
"""Returns the terminal profile from TERM_PROFILE or
|
|
derrives through detecting the profile"""
|
|
if not force and TERM_VAR in os.environ:
|
|
return os.environ[TERM_VAR]
|
|
|
|
# If over SSH, quit. Colorschemes should instead be forwarded
|
|
if is_ssh():
|
|
print(
|
|
("Warning: Cannot derive colors. "
|
|
"SSH sessions won't allow detecting the terminal profile."
|
|
f" Instead forward {TERM_VAR} from your source machine."),
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(0)
|
|
|
|
term_program = os.environ.get("TERM_PROGRAM")
|
|
if term_program == "Apple_Terminal":
|
|
tty_output = check_output(["tty"]).strip()
|
|
tty = str(tty_output, encoding="utf-8")
|
|
code, stdout, _ = run_applescript(
|
|
TERMINAL_SETTINGS_SCRIPT.format(tty),
|
|
)
|
|
if code:
|
|
raise SystemError("Could not get results from applescript")
|
|
return stdout
|
|
if term_program == "iTerm.app":
|
|
if "ITERM_PROFILE" in os.environ:
|
|
return os.environ["ITERM_PROFILE"]
|
|
raise TermProfileError("Using iTerm but no profile found")
|
|
if term_program == "Alacritty":
|
|
return "Alacritty"
|
|
|
|
if os.environ.get("GNOME_TERMINAL_SCREEN") is not None:
|
|
return "Gnome Terminal"
|
|
|
|
if os.environ.get("KITTY_WINDOW_ID") is not None:
|
|
return os.environ.get("KITTY_THEME", "Unknown")
|
|
|
|
# If we got this far, we don't know what to do
|
|
raise TermProfileError(f"Unknown terminal {term_program}")
|
|
|
|
|
|
def get_vim_colorscheme(
|
|
terminal_profile: str,
|
|
_force_dark=False,
|
|
force=False,
|
|
) -> str:
|
|
"""Returns the best colorscheme for a given terminal profile"""
|
|
if not force and VIM_VAR in os.environ:
|
|
return os.environ[VIM_VAR]
|
|
|
|
colorscheme = "wombat256mod"
|
|
|
|
if terminal_profile in "Wombat":
|
|
colorscheme = "wombat256mod"
|
|
elif terminal_profile == "Alacritty":
|
|
colorscheme = "wombat256mod"
|
|
elif terminal_profile == "Yosemite Light":
|
|
colorscheme = "morning"
|
|
elif terminal_profile == "Yosemite Dark":
|
|
colorscheme = "vividchalk"
|
|
elif terminal_profile == "Basic":
|
|
colorscheme = "default"
|
|
elif "Solarized" in terminal_profile:
|
|
colorscheme = "solarized"
|
|
|
|
return colorscheme
|
|
|
|
|
|
def get_nvim_colorscheme(
|
|
terminal_profile: str,
|
|
_force_dark=False,
|
|
force=False,
|
|
) -> str:
|
|
"""Returns the best colorscheme for a given terminal profile"""
|
|
if not force and NVIM_VAR in os.environ:
|
|
return os.environ[NVIM_VAR]
|
|
|
|
colorscheme = "wombat_lush"
|
|
|
|
if terminal_profile in "Wombat":
|
|
colorscheme = "wombat_lush"
|
|
elif terminal_profile == "Alacritty":
|
|
colorscheme = "wombat_lush"
|
|
elif terminal_profile == "Yosemite Light":
|
|
colorscheme = "morning"
|
|
elif terminal_profile == "Yosemite Dark":
|
|
colorscheme = "vividchalk"
|
|
elif terminal_profile == "Basic":
|
|
colorscheme = "default"
|
|
elif "Solarized" in terminal_profile:
|
|
colorscheme = "solarized"
|
|
elif "Tokyo Night" in terminal_profile:
|
|
colorscheme = "tokyonight"
|
|
|
|
return colorscheme
|
|
|
|
|
|
def get_bat_theme(terminal_profile: str, force_dark=False, force=False) -> str:
|
|
"""Returns the best matched bat theme for the terminal"""
|
|
if not force and BAT_VAR in os.environ:
|
|
return os.environ[BAT_VAR]
|
|
|
|
# Determine if this is a dark theme
|
|
is_dark = force_dark or "dark" in terminal_profile.lower()
|
|
|
|
bat_theme = "DarkNeon" if is_dark else "ansi-light"
|
|
|
|
if "Wombat" in terminal_profile:
|
|
bat_theme = "DarkNeon"
|
|
elif terminal_profile == "Alacritty":
|
|
bat_theme = "DarkNeon"
|
|
elif "Solarized" in terminal_profile:
|
|
if is_dark:
|
|
bat_theme = "Solarized (dark)"
|
|
else:
|
|
bat_theme = "Solarized (light)"
|
|
|
|
return bat_theme
|
|
|
|
|
|
def get_fish_theme(
|
|
terminal_profile: str,
|
|
force_dark=False,
|
|
force=False,
|
|
) -> Optional[str]:
|
|
"""Returns the best matched fish theme for the terminal"""
|
|
if not force and FISH_VAR in os.environ:
|
|
return os.environ[FISH_VAR]
|
|
|
|
# Determine if this is a dark theme
|
|
is_dark = force_dark or "dark" in terminal_profile.lower()
|
|
|
|
fish_theme: Optional[str] = None
|
|
|
|
if "Wombat" in terminal_profile:
|
|
fish_theme = "wombat"
|
|
elif terminal_profile == "Alacritty":
|
|
fish_theme = "wombat"
|
|
elif "Solarized" in terminal_profile:
|
|
if is_dark:
|
|
fish_theme = "solarized_dark"
|
|
else:
|
|
fish_theme = "solarized_light"
|
|
elif "Tokyo Night" in terminal_profile:
|
|
if is_dark:
|
|
fish_theme = "fish_tokyonight_night"
|
|
else:
|
|
fish_theme = "fish_tokyonight_day"
|
|
|
|
return fish_theme
|
|
|
|
|
|
def parse_args(**args) -> argparse.Namespace:
|
|
"""Parse and return args from the terminal"""
|
|
parser = argparse.ArgumentParser(
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
description=textwrap.dedent("""
|
|
This tool inspects the current terminal to determine which
|
|
colorschemes is in use and which themes should should be used by
|
|
various other tools, like vim and bat.
|
|
|
|
When running this command, the variables will be printed such
|
|
that they can be sourced by your shell.
|
|
|
|
If your terminal themes are not derrivable, or you'd like to
|
|
hard code for performance reasons (detecting Terminal.app profiles
|
|
is kinda slow), the variables to export are:
|
|
|
|
TERM_PROFILE Profile name of your current terminal. Used
|
|
to derive the other colorschemes.
|
|
|
|
VIM_COLOR Colorscheme to set in Vim. This can be used to
|
|
set your theme per example:
|
|
https://github.com/ViViDboarder/vim-settings/blob/92d7a08690e044f833b3651c293a85eb2b0796ab/vim/rc/ui.rc.vim#L17
|
|
|
|
NVIM_COLOR Colorscheme to set in Neovim. Sometimes this
|
|
may be different than Vim.
|
|
|
|
BAT_THEME Theme to be used by bat when printing syntax
|
|
highlighted files.
|
|
|
|
FISH_THEME Theme to be used by fish shell. Rounds out the
|
|
last of the animal based themes.
|
|
"""),
|
|
epilog=textwrap.dedent("""\
|
|
To source all variable into your shell, run:
|
|
eval $(derive_colors.py --export)
|
|
If running fish, you must instead use:
|
|
eval (derive_colors.py --export --fish)
|
|
"""))
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument(
|
|
"--print-term",
|
|
action="store_true",
|
|
help="Print only the value of the terminal profile",
|
|
)
|
|
group.add_argument(
|
|
"--print-vim",
|
|
action="store_true",
|
|
help="Print only the value of the vim colorscheme",
|
|
)
|
|
group.add_argument(
|
|
"--print-bat",
|
|
action="store_true",
|
|
help="Print only the value of the bat theme",
|
|
)
|
|
group.add_argument(
|
|
"--print-fish",
|
|
action="store_true",
|
|
help="Print only the value of the fish theme",
|
|
)
|
|
parser.add_argument(
|
|
"--force",
|
|
action="store_true",
|
|
help="Force reparsing all variables",
|
|
)
|
|
parser.add_argument(
|
|
"--dark",
|
|
action="store_true",
|
|
help="Get dark versions of anything available",
|
|
)
|
|
parser.add_argument(
|
|
"--fish",
|
|
action="store_true",
|
|
help="Print fish compatible env commands",
|
|
)
|
|
parser.add_argument(
|
|
"--export",
|
|
action="store_true",
|
|
help="Print env variables to be exported",
|
|
)
|
|
return parser.parse_args(**args)
|
|
|
|
|
|
def print_all_env(force=False, force_dark=False, export=False, fish=False):
|
|
"""Print all variables in env format"""
|
|
term_profile = ""
|
|
try:
|
|
term_profile = get_terminal_profile(force=force)
|
|
print_env(TERM_VAR, term_profile, export=export, fish=fish)
|
|
except TermProfileError as e:
|
|
warn(e)
|
|
|
|
vim_colors = get_vim_colorscheme(
|
|
term_profile,
|
|
_force_dark=force_dark,
|
|
force=force,
|
|
)
|
|
print_env(VIM_VAR, vim_colors, export=export, fish=fish)
|
|
|
|
nvim_colors = get_nvim_colorscheme(
|
|
term_profile,
|
|
_force_dark=force_dark,
|
|
force=force,
|
|
)
|
|
print_env(NVIM_VAR, nvim_colors, export=export, fish=fish)
|
|
|
|
bat_theme = get_bat_theme(term_profile, force_dark=force_dark, force=force)
|
|
print_env(BAT_VAR, bat_theme, export=export, fish=fish)
|
|
|
|
# Fish theme is optional, so don't print if None
|
|
fish_theme = get_fish_theme(
|
|
term_profile,
|
|
force_dark=force_dark,
|
|
force=force,
|
|
)
|
|
if fish_theme is not None:
|
|
print_env(FISH_VAR, fish_theme, export=export, fish=fish)
|
|
|
|
|
|
def print_env(var: str, val: str, export=False, fish=False):
|
|
"""Print variable in env format"""
|
|
if not fish:
|
|
if export:
|
|
print(f'export {var}="{val}"')
|
|
else:
|
|
print(f'{var}="{val}"')
|
|
elif fish:
|
|
if export:
|
|
print(f'set -gx {var} "{val}";')
|
|
else:
|
|
print(f'set -g {var} "{val}";')
|
|
|
|
|
|
def is_ssh() -> bool:
|
|
"""Detect if we're in an SSH session"""
|
|
if os.environ.get("SSH_TTY"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main function"""
|
|
args = parse_args()
|
|
if args.print_term:
|
|
term_profile = get_terminal_profile(force=args.force)
|
|
print(term_profile)
|
|
elif args.print_vim:
|
|
term_profile = get_terminal_profile(force=args.force)
|
|
vim_colors = get_vim_colorscheme(
|
|
term_profile,
|
|
_force_dark=args.dark,
|
|
force=args.force,
|
|
)
|
|
print(vim_colors)
|
|
nvim_colors = get_nvim_colorscheme(
|
|
term_profile,
|
|
_force_dark=args.dark,
|
|
force=args.force,
|
|
)
|
|
print(nvim_colors)
|
|
elif args.print_bat:
|
|
term_profile = get_terminal_profile(force=args.force)
|
|
bat_theme = get_bat_theme(
|
|
term_profile,
|
|
force_dark=args.dark,
|
|
force=args.force,
|
|
)
|
|
print(bat_theme)
|
|
elif args.print_fish:
|
|
term_profile = get_terminal_profile(force=args.force)
|
|
fish_theme = get_fish_theme(
|
|
term_profile,
|
|
force_dark=args.dark,
|
|
force=args.force,
|
|
)
|
|
print(fish_theme)
|
|
else:
|
|
print_all_env(
|
|
force=args.force,
|
|
force_dark=args.dark,
|
|
fish=args.fish,
|
|
export=args.export,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|