Skip to content

Commit 820d9da

Browse files
committed
feat!: auto switch between dark and light icons/flavors based on terminal backgrounds (#1946)
1 parent 1277b01 commit 820d9da

File tree

30 files changed

+1776
-862
lines changed

30 files changed

+1776
-862
lines changed

cspell.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","Sysinfo","ffprobe","vframes"],"version":"0.2"}
1+
{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","Sysinfo","ffprobe","vframes","luma"],"language":"en"}

scripts/icons/generate.lua

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@ function dump(map)
2121
list[#list + 1] = { name = k, text = v.icon, fg_dark = v.fg_dark, fg_light = v.fg_light }
2222
end
2323
table.sort(list, function(a, b) return a.name:lower() < b.name:lower() end)
24+
local dark, light = "", ""
2425
for _, v in ipairs(list) do
2526
-- stylua: ignore
26-
print(string.format('\t{ name = "%s", text = "%s", fg_dark = "%s", fg_light = "%s" },', v.name, v.text, v.fg_dark, v.fg_light))
27+
dark = dark .. string.format('\t{ name = "%s", text = "%s", fg = "%s" },\n', v.name, v.text, v.fg_dark)
28+
light = light .. string.format('\t{ name = "%s", text = "%s", fg = "%s" },\n', v.name, v.text, v.fg_light)
2729
end
30+
return dark, light
2831
end
2932

30-
print("files = [")
31-
dump(rearrange("files"))
32-
print("]")
33+
function save(typ, files, exts)
34+
local p = string.format("../../yazi-config/preset/theme+%s.toml", typ)
35+
local s = io.open(p, "r"):read("*a")
36+
s = s:gsub("files = %[\n(.-)\n%]", string.format("files = [\n%s]", files))
37+
s = s:gsub("exts = %[\n(.-)\n%]", string.format("exts = [\n%s]", exts))
38+
io.open(p, "w"):write(s)
39+
end
40+
41+
local dark_files, light_files = dump(rearrange("files"))
42+
local dark_exts, light_exts = dump(rearrange("exts"))
3343

34-
print("exts = [")
35-
dump(rearrange("exts"))
36-
print("]")
44+
save("dark", dark_files, dark_exts)
45+
save("light", light_files, light_exts)

yazi-adapter/src/adapter.rs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ use ratatui::layout::Rect;
55
use tracing::warn;
66
use yazi_shared::env_exists;
77

8-
use super::{Iip, Kgp, KgpOld};
9-
use crate::{Chafa, Emulator, SHOWN, Sixel, TMUX, Ueberzug, WSL};
8+
use crate::{Brand, Emulator, SHOWN, TMUX, WSL, drivers};
109

1110
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1211
pub enum Adapter {
@@ -42,12 +41,12 @@ impl Adapter {
4241
}
4342

4443
match self {
45-
Self::Kgp => Kgp::image_show(path, max).await,
46-
Self::KgpOld => KgpOld::image_show(path, max).await,
47-
Self::Iip => Iip::image_show(path, max).await,
48-
Self::Sixel => Sixel::image_show(path, max).await,
49-
Self::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
50-
Self::Chafa => Chafa::image_show(path, max).await,
44+
Self::Kgp => drivers::Kgp::image_show(path, max).await,
45+
Self::KgpOld => drivers::KgpOld::image_show(path, max).await,
46+
Self::Iip => drivers::Iip::image_show(path, max).await,
47+
Self::Sixel => drivers::Sixel::image_show(path, max).await,
48+
Self::X11 | Self::Wayland => drivers::Ueberzug::image_show(path, max).await,
49+
Self::Chafa => drivers::Chafa::image_show(path, max).await,
5150
}
5251
}
5352

@@ -57,12 +56,12 @@ impl Adapter {
5756

5857
pub fn image_erase(self, area: Rect) -> Result<()> {
5958
match self {
60-
Self::Kgp => Kgp::image_erase(area),
61-
Self::KgpOld => KgpOld::image_erase(area),
62-
Self::Iip => Iip::image_erase(area),
63-
Self::Sixel => Sixel::image_erase(area),
64-
Self::X11 | Self::Wayland => Ueberzug::image_erase(area),
65-
Self::Chafa => Chafa::image_erase(area),
59+
Self::Kgp => drivers::Kgp::image_erase(area),
60+
Self::KgpOld => drivers::KgpOld::image_erase(area),
61+
Self::Iip => drivers::Iip::image_erase(area),
62+
Self::Sixel => drivers::Sixel::image_erase(area),
63+
Self::X11 | Self::Wayland => drivers::Ueberzug::image_erase(area),
64+
Self::Chafa => drivers::Chafa::image_erase(area),
6665
}
6766
}
6867

@@ -72,7 +71,7 @@ impl Adapter {
7271
#[inline]
7372
pub(super) fn shown_store(area: Rect) { SHOWN.set(Some(area)); }
7473

75-
pub(super) fn start(self) { Ueberzug::start(self); }
74+
pub(super) fn start(self) { drivers::Ueberzug::start(self); }
7675

7776
#[inline]
7877
pub(super) fn needs_ueberzug(self) -> bool {
@@ -81,15 +80,14 @@ impl Adapter {
8180
}
8281

8382
impl Adapter {
84-
pub fn matches() -> Self {
85-
let emulator = Emulator::detect();
86-
if matches!(emulator, Emulator::Microsoft) {
83+
pub fn matches(emulator: Emulator) -> Self {
84+
if matches!(emulator.kind.left(), Some(Brand::Microsoft)) {
8785
return Self::Sixel;
88-
} else if *WSL && matches!(emulator, Emulator::WezTerm) {
86+
} else if *WSL && matches!(emulator.kind.left(), Some(Brand::WezTerm)) {
8987
return Self::KgpOld;
9088
}
9189

92-
let mut protocols = emulator.adapters();
90+
let mut protocols = emulator.adapters().to_owned();
9391
#[cfg(windows)]
9492
protocols.retain(|p| *p == Self::Iip);
9593
if env_exists("ZELLIJ_SESSION_NAME") {
@@ -101,7 +99,7 @@ impl Adapter {
10199
return *p;
102100
}
103101

104-
let supported_compositor = Ueberzug::supported_compositor();
102+
let supported_compositor = drivers::Ueberzug::supported_compositor();
105103
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() {
106104
"x11" => return Self::X11,
107105
"wayland" if supported_compositor => return Self::Wayland,

yazi-adapter/src/brand.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use tracing::warn;
2+
use yazi_shared::env_exists;
3+
4+
use crate::Mux;
5+
6+
#[derive(Clone, Copy, Debug)]
7+
pub enum Brand {
8+
Kitty,
9+
Konsole,
10+
Iterm2,
11+
WezTerm,
12+
Foot,
13+
Ghostty,
14+
Microsoft,
15+
Rio,
16+
BlackBox,
17+
VSCode,
18+
Tabby,
19+
Hyper,
20+
Mintty,
21+
Neovim,
22+
Apple,
23+
Urxvt,
24+
}
25+
26+
impl Brand {
27+
pub fn from_env() -> Option<Self> {
28+
use Brand as B;
29+
30+
if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") {
31+
return Some(Self::Neovim);
32+
}
33+
34+
let vars = [
35+
("KITTY_WINDOW_ID", B::Kitty),
36+
("KONSOLE_VERSION", B::Konsole),
37+
("ITERM_SESSION_ID", B::Iterm2),
38+
("WEZTERM_EXECUTABLE", B::WezTerm),
39+
("GHOSTTY_RESOURCES_DIR", B::Ghostty),
40+
("WT_Session", B::Microsoft),
41+
("VSCODE_INJECTION", B::VSCode),
42+
("TABBY_CONFIG_DIRECTORY", B::Tabby),
43+
];
44+
match vars.into_iter().find(|&(s, _)| env_exists(s)) {
45+
Some((_, brand)) => return Some(brand),
46+
None => warn!("[Adapter] No special environment variables detected"),
47+
}
48+
49+
let (term, program) = B::env();
50+
match program.as_str() {
51+
"iTerm.app" => return Some(B::Iterm2),
52+
"WezTerm" => return Some(B::WezTerm),
53+
"ghostty" => return Some(B::Ghostty),
54+
"rio" => return Some(B::Rio),
55+
"BlackBox" => return Some(B::BlackBox),
56+
"vscode" => return Some(B::VSCode),
57+
"Tabby" => return Some(B::Tabby),
58+
"Hyper" => return Some(B::Hyper),
59+
"mintty" => return Some(B::Mintty),
60+
"Apple_Terminal" => return Some(B::Apple),
61+
_ => warn!("[Adapter] Unknown TERM_PROGRAM: {program}"),
62+
}
63+
match term.as_str() {
64+
"xterm-kitty" => return Some(B::Kitty),
65+
"foot" => return Some(B::Foot),
66+
"foot-extra" => return Some(B::Foot),
67+
"xterm-ghostty" => return Some(B::Ghostty),
68+
"rio" => return Some(B::Rio),
69+
"rxvt-unicode-256color" => return Some(B::Urxvt),
70+
_ => warn!("[Adapter] Unknown TERM: {term}"),
71+
}
72+
None
73+
}
74+
75+
pub(super) fn from_csi(resp: &str) -> Option<Self> {
76+
let names = [
77+
("kitty", Self::Kitty),
78+
("Konsole", Self::Konsole),
79+
("iTerm2", Self::Iterm2),
80+
("WezTerm", Self::WezTerm),
81+
("foot", Self::Foot),
82+
("ghostty", Self::Ghostty),
83+
];
84+
names.into_iter().find(|&(n, _)| resp.contains(n)).map(|(_, b)| b)
85+
}
86+
87+
pub(super) fn adapters(self) -> &'static [crate::Adapter] {
88+
use Brand as B;
89+
90+
use crate::Adapter as A;
91+
92+
match self {
93+
B::Kitty => &[A::Kgp],
94+
B::Konsole => &[A::KgpOld],
95+
B::Iterm2 => &[A::Iip, A::Sixel],
96+
B::WezTerm => &[A::Iip, A::Sixel],
97+
B::Foot => &[A::Sixel],
98+
B::Ghostty => &[A::Kgp],
99+
B::Microsoft => &[A::Sixel],
100+
B::Rio => &[A::Iip, A::Sixel],
101+
B::BlackBox => &[A::Sixel],
102+
B::VSCode => &[A::Iip, A::Sixel],
103+
B::Tabby => &[A::Iip, A::Sixel],
104+
B::Hyper => &[A::Iip, A::Sixel],
105+
B::Mintty => &[A::Iip],
106+
B::Neovim => &[],
107+
B::Apple => &[],
108+
B::Urxvt => &[],
109+
}
110+
}
111+
112+
fn env() -> (String, String) {
113+
let (term, program) = Mux::term_program();
114+
(
115+
term.unwrap_or(std::env::var("TERM").unwrap_or_default()),
116+
program.unwrap_or(std::env::var("TERM_PROGRAM").unwrap_or_default()),
117+
)
118+
}
119+
}

yazi-adapter/src/chafa.rs renamed to yazi-adapter/src/drivers/chafa.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ use tokio::process::Command;
88

99
use crate::{Adapter, Emulator};
1010

11-
pub(super) struct Chafa;
11+
pub(crate) struct Chafa;
1212

1313
impl Chafa {
14-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
14+
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
1515
let output = Command::new("chafa")
1616
.args([
1717
"-f",
@@ -64,7 +64,7 @@ impl Chafa {
6464
})
6565
}
6666

67-
pub(super) fn image_erase(area: Rect) -> Result<()> {
67+
pub(crate) fn image_erase(area: Rect) -> Result<()> {
6868
let s = " ".repeat(area.width as usize);
6969
Emulator::move_lock((0, 0), |stderr| {
7070
for y in area.top()..area.bottom() {

yazi-adapter/src/iip.rs renamed to yazi-adapter/src/drivers/iip.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEn
77
use ratatui::layout::Rect;
88
use yazi_config::PREVIEW;
99

10-
use super::image::Image;
11-
use crate::{CLOSE, Emulator, START, adapter::Adapter};
10+
use crate::{CLOSE, Emulator, Image, START, adapter::Adapter};
1211

13-
pub(super) struct Iip;
12+
pub(crate) struct Iip;
1413

1514
impl Iip {
16-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
15+
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
1716
let img = Image::downscale(path, max).await?;
1817
let area = Image::pixel_area((img.width(), img.height()), max);
1918
let b = Self::encode(img).await?;
@@ -26,7 +25,7 @@ impl Iip {
2625
})
2726
}
2827

29-
pub(super) fn image_erase(area: Rect) -> Result<()> {
28+
pub(crate) fn image_erase(area: Rect) -> Result<()> {
3029
let s = " ".repeat(area.width as usize);
3130
Emulator::move_lock((0, 0), |stderr| {
3231
for y in area.top()..area.bottom() {

yazi-adapter/src/kgp.rs renamed to yazi-adapter/src/drivers/kgp.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use crossterm::{cursor::MoveTo, queue};
77
use image::DynamicImage;
88
use ratatui::layout::Rect;
99

10-
use super::image::Image;
11-
use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter};
10+
use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter, image::Image};
1211

1312
static DIACRITICS: [char; 297] = [
1413
'\u{0305}',
@@ -310,10 +309,10 @@ static DIACRITICS: [char; 297] = [
310309
'\u{1D244}',
311310
];
312311

313-
pub(super) struct Kgp;
312+
pub(crate) struct Kgp;
314313

315314
impl Kgp {
316-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
315+
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
317316
let img = Image::downscale(path, max).await?;
318317
let area = Image::pixel_area((img.width(), img.height()), max);
319318

@@ -329,7 +328,7 @@ impl Kgp {
329328
})
330329
}
331330

332-
pub(super) fn image_erase(area: Rect) -> Result<()> {
331+
pub(crate) fn image_erase(area: Rect) -> Result<()> {
333332
let s = " ".repeat(area.width as usize);
334333
Emulator::move_lock((0, 0), |stderr| {
335334
for y in area.top()..area.bottom() {

yazi-adapter/src/kgp_old.rs renamed to yazi-adapter/src/drivers/kgp_old.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ use base64::{Engine, engine::general_purpose};
66
use image::DynamicImage;
77
use ratatui::layout::Rect;
88

9-
use super::image::Image;
10-
use crate::{CLOSE, ESCAPE, Emulator, START, adapter::Adapter};
9+
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
1110

12-
pub(super) struct KgpOld;
11+
pub(crate) struct KgpOld;
1312

1413
impl KgpOld {
15-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
14+
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
1615
let img = Image::downscale(path, max).await?;
1716
let area = Image::pixel_area((img.width(), img.height()), max);
1817
let b = Self::encode(img).await?;
@@ -26,7 +25,7 @@ impl KgpOld {
2625
}
2726

2827
#[inline]
29-
pub(super) fn image_erase(_: Rect) -> Result<()> {
28+
pub(crate) fn image_erase(_: Rect) -> Result<()> {
3029
let mut stderr = LineWriter::new(stderr());
3130
write!(stderr, "{}_Gq=2,a=d,d=A{}\\{}", START, ESCAPE, CLOSE)?;
3231
stderr.flush()?;

yazi-adapter/src/drivers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
yazi_macro::mod_flat!(chafa iip kgp kgp_old sixel ueberzug);

yazi-adapter/src/sixel.rs renamed to yazi-adapter/src/drivers/sixel.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use yazi_config::PREVIEW;
99

1010
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
1111

12-
pub(super) struct Sixel;
12+
pub(crate) struct Sixel;
1313

1414
impl Sixel {
15-
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
15+
pub(crate) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
1616
let img = Image::downscale(path, max).await?;
1717
let area = Image::pixel_area((img.width(), img.height()), max);
1818
let b = Self::encode(img).await?;
@@ -25,7 +25,7 @@ impl Sixel {
2525
})
2626
}
2727

28-
pub(super) fn image_erase(area: Rect) -> Result<()> {
28+
pub(crate) fn image_erase(area: Rect) -> Result<()> {
2929
let s = " ".repeat(area.width as usize);
3030
Emulator::move_lock((0, 0), |stderr| {
3131
for y in area.top()..area.bottom() {

0 commit comments

Comments
 (0)