diff --git a/cspell.json b/cspell.json index c1f5d8e5..7a8cc8e8 100644 --- a/cspell.json +++ b/cspell.json @@ -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"} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/yazi-adapter/src/adapter.rs b/yazi-adapter/src/adapter.rs index 1de41e75..a3537ea7 100644 --- a/yazi-adapter/src/adapter.rs +++ b/yazi-adapter/src/adapter.rs @@ -5,7 +5,7 @@ use ratatui::layout::Rect; use tracing::warn; use yazi_shared::env_exists; -use crate::{Emulator, SHOWN, TMUX, WSL, drivers}; +use crate::{Brand, Emulator, SHOWN, TMUX, WSL, drivers}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Adapter { @@ -80,15 +80,14 @@ impl Adapter { } impl Adapter { - pub fn matches() -> Self { - let emulator = Emulator::detect(); - if matches!(emulator, Emulator::Microsoft) { + pub fn matches(emulator: Emulator) -> Self { + if matches!(emulator.kind.left(), Some(Brand::Microsoft)) { return Self::Sixel; - } else if *WSL && matches!(emulator, Emulator::WezTerm) { + } else if *WSL && matches!(emulator.kind.left(), Some(Brand::WezTerm)) { return Self::KgpOld; } - let mut protocols = emulator.adapters(); + let mut protocols = emulator.adapters().to_owned(); #[cfg(windows)] protocols.retain(|p| *p == Self::Iip); if env_exists("ZELLIJ_SESSION_NAME") { diff --git a/yazi-adapter/src/brand.rs b/yazi-adapter/src/brand.rs new file mode 100644 index 00000000..5aa7b69e --- /dev/null +++ b/yazi-adapter/src/brand.rs @@ -0,0 +1,119 @@ +use tracing::warn; +use yazi_shared::env_exists; + +use crate::Mux; + +#[derive(Clone, Copy, Debug)] +pub enum Brand { + Kitty, + Konsole, + Iterm2, + WezTerm, + Foot, + Ghostty, + Microsoft, + Rio, + BlackBox, + VSCode, + Tabby, + Hyper, + Mintty, + Neovim, + Apple, + Urxvt, +} + +impl Brand { + pub fn from_env() -> Option { + use Brand as B; + + if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") { + return Some(Self::Neovim); + } + + let vars = [ + ("KITTY_WINDOW_ID", B::Kitty), + ("KONSOLE_VERSION", B::Konsole), + ("ITERM_SESSION_ID", B::Iterm2), + ("WEZTERM_EXECUTABLE", B::WezTerm), + ("GHOSTTY_RESOURCES_DIR", B::Ghostty), + ("WT_Session", B::Microsoft), + ("VSCODE_INJECTION", B::VSCode), + ("TABBY_CONFIG_DIRECTORY", B::Tabby), + ]; + match vars.into_iter().find(|&(s, _)| env_exists(s)) { + Some((_, brand)) => return Some(brand), + None => warn!("[Adapter] No special environment variables detected"), + } + + let (term, program) = B::env(); + match program.as_str() { + "iTerm.app" => return Some(B::Iterm2), + "WezTerm" => return Some(B::WezTerm), + "ghostty" => return Some(B::Ghostty), + "rio" => return Some(B::Rio), + "BlackBox" => return Some(B::BlackBox), + "vscode" => return Some(B::VSCode), + "Tabby" => return Some(B::Tabby), + "Hyper" => return Some(B::Hyper), + "mintty" => return Some(B::Mintty), + "Apple_Terminal" => return Some(B::Apple), + _ => warn!("[Adapter] Unknown TERM_PROGRAM: {program}"), + } + match term.as_str() { + "xterm-kitty" => return Some(B::Kitty), + "foot" => return Some(B::Foot), + "foot-extra" => return Some(B::Foot), + "xterm-ghostty" => return Some(B::Ghostty), + "rio" => return Some(B::Rio), + "rxvt-unicode-256color" => return Some(B::Urxvt), + _ => warn!("[Adapter] Unknown TERM: {term}"), + } + None + } + + pub(super) fn from_csi(resp: &str) -> Option { + let names = [ + ("kitty", Self::Kitty), + ("Konsole", Self::Konsole), + ("iTerm2", Self::Iterm2), + ("WezTerm", Self::WezTerm), + ("foot", Self::Foot), + ("ghostty", Self::Ghostty), + ]; + names.into_iter().find(|&(n, _)| resp.contains(n)).map(|(_, b)| b) + } + + pub(super) fn adapters(self) -> &'static [crate::Adapter] { + use Brand as B; + + use crate::Adapter as A; + + match self { + B::Kitty => &[A::Kgp], + B::Konsole => &[A::KgpOld], + B::Iterm2 => &[A::Iip, A::Sixel], + B::WezTerm => &[A::Iip, A::Sixel], + B::Foot => &[A::Sixel], + B::Ghostty => &[A::Kgp], + B::Microsoft => &[A::Sixel], + B::Rio => &[A::Iip, A::Sixel], + B::BlackBox => &[A::Sixel], + B::VSCode => &[A::Iip, A::Sixel], + B::Tabby => &[A::Iip, A::Sixel], + B::Hyper => &[A::Iip, A::Sixel], + B::Mintty => &[A::Iip], + B::Neovim => &[], + B::Apple => &[], + B::Urxvt => &[], + } + } + + fn env() -> (String, String) { + let (term, program) = Mux::term_program(); + ( + term.unwrap_or(std::env::var("TERM").unwrap_or_default()), + program.unwrap_or(std::env::var("TERM_PROGRAM").unwrap_or_default()), + ) + } +} diff --git a/yazi-adapter/src/emulator.rs b/yazi-adapter/src/emulator.rs index 4c23460f..8e12aa3e 100644 --- a/yazi-adapter/src/emulator.rs +++ b/yazi-adapter/src/emulator.rs @@ -4,150 +4,61 @@ use anyhow::{Result, bail}; use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}}; use scopeguard::defer; use tokio::{io::{AsyncReadExt, BufReader}, time::timeout}; -use tracing::{error, warn}; -use yazi_shared::env_exists; +use tracing::{debug, error, warn}; +use yazi_shared::Either; -use crate::{Adapter, Mux, TMUX}; +use crate::{Adapter, Brand, Mux, TMUX, Unknown}; -#[derive(Clone, Debug)] -pub enum Emulator { - Unknown(Vec), - Kitty, - Konsole, - Iterm2, - WezTerm, - Foot, - Ghostty, - Microsoft, - Rio, - BlackBox, - VSCode, - Tabby, - Hyper, - Mintty, - Neovim, - Apple, - Urxvt, +#[derive(Clone, Copy, Debug)] +pub struct Emulator { + pub kind: Either, + pub light: bool, } -impl Emulator { - pub fn adapters(self) -> Vec { - match self { - Self::Unknown(adapters) => adapters, - Self::Kitty => vec![Adapter::Kgp], - Self::Konsole => vec![Adapter::KgpOld], - Self::Iterm2 => vec![Adapter::Iip, Adapter::Sixel], - Self::WezTerm => vec![Adapter::Iip, Adapter::Sixel], - Self::Foot => vec![Adapter::Sixel], - Self::Ghostty => vec![Adapter::Kgp], - Self::Microsoft => vec![Adapter::Sixel], - Self::Rio => vec![Adapter::Iip, Adapter::Sixel], - Self::BlackBox => vec![Adapter::Sixel], - Self::VSCode => vec![Adapter::Iip, Adapter::Sixel], - Self::Tabby => vec![Adapter::Iip, Adapter::Sixel], - Self::Hyper => vec![Adapter::Iip, Adapter::Sixel], - Self::Mintty => vec![Adapter::Iip], - Self::Neovim => vec![], - Self::Apple => vec![], - Self::Urxvt => vec![], - } - } +impl Default for Emulator { + fn default() -> Self { Self { kind: Either::Right(Unknown::default()), light: false } } } impl Emulator { pub fn detect() -> Self { - if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") { - return Self::Neovim; + if let Some(brand) = Brand::from_env() { + Self { kind: Either::Left(brand), light: Self::detect_base().unwrap_or_default().light } + } else { + Self::detect_full().unwrap_or_default() } - - let vars = [ - ("KITTY_WINDOW_ID", Self::Kitty), - ("KONSOLE_VERSION", Self::Konsole), - ("ITERM_SESSION_ID", Self::Iterm2), - ("WEZTERM_EXECUTABLE", Self::WezTerm), - ("GHOSTTY_RESOURCES_DIR", Self::Ghostty), - ("WT_Session", Self::Microsoft), - ("VSCODE_INJECTION", Self::VSCode), - ("TABBY_CONFIG_DIRECTORY", Self::Tabby), - ]; - match vars.into_iter().find(|(env, _)| env_exists(env)) { - Some((_, emulator)) => return emulator, - None => warn!("[Adapter] No special environment variables detected"), - } - - let (term, program) = Self::via_env(); - match program.as_str() { - "iTerm.app" => return Self::Iterm2, - "WezTerm" => return Self::WezTerm, - "ghostty" => return Self::Ghostty, - "rio" => return Self::Rio, - "BlackBox" => return Self::BlackBox, - "vscode" => return Self::VSCode, - "Tabby" => return Self::Tabby, - "Hyper" => return Self::Hyper, - "mintty" => return Self::Mintty, - "Apple_Terminal" => return Self::Apple, - _ => warn!("[Adapter] Unknown TERM_PROGRAM: {program}"), - } - match term.as_str() { - "xterm-kitty" => return Self::Kitty, - "foot" => return Self::Foot, - "foot-extra" => return Self::Foot, - "xterm-ghostty" => return Self::Ghostty, - "rio" => return Self::Rio, - "rxvt-unicode-256color" => return Self::Urxvt, - _ => warn!("[Adapter] Unknown TERM: {term}"), - } - - Self::via_csi().unwrap_or(Self::Unknown(vec![])) } - pub fn via_env() -> (String, String) { - let (term, program) = Mux::term_program(); - ( - term.unwrap_or(std::env::var("TERM").unwrap_or_default()), - program.unwrap_or(std::env::var("TERM_PROGRAM").unwrap_or_default()), - ) - } - - pub fn via_csi() -> Result { + pub fn detect_full() -> Result { defer! { disable_raw_mode().ok(); } enable_raw_mode()?; execute!( LineWriter::new(stderr()), SavePosition, - Print(Mux::csi("\x1b[>q")), // Request terminal version Print(Mux::csi("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")), // Detect KGP - Print(Mux::csi("\x1b[c")), // Request device attributes + Print(Mux::csi("\x1b[>q")), // Request terminal version + Print(Mux::csi("\x1b[c")), // Request device attributes RestorePosition )?; let resp = futures::executor::block_on(Self::read_until_da1()); - let names = [ - ("kitty", Self::Kitty), - ("Konsole", Self::Konsole), - ("iTerm2", Self::Iterm2), - ("WezTerm", Self::WezTerm), - ("foot", Self::Foot), - ("ghostty", Self::Ghostty), - ]; + let kind = if let Some(brand) = Brand::from_csi(&resp) { + Either::Left(brand) + } else { + Either::Right(Unknown { + kgp: resp.contains("\x1b_Gi=31;OK"), + sixel: ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| resp.contains(s)), + }) + }; - for (name, emulator) in names.iter() { - if resp.contains(name) { - return Ok(emulator.clone()); - } - } + Ok(Self { kind, light: Self::light_bg(&resp).unwrap_or_default() }) + } - let mut adapters = Vec::with_capacity(2); - if resp.contains("\x1b_Gi=31;OK") { - adapters.push(Adapter::KgpOld); + pub fn adapters(self) -> &'static [Adapter] { + match self.kind { + Either::Left(brand) => brand.adapters(), + Either::Right(unknown) => unknown.adapters(), } - if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| resp.contains(s)) { - adapters.push(Adapter::Sixel); - } - - Ok(Self::Unknown(adapters)) } pub fn move_lock((x, y): (u16, u16), cb: F) -> Result @@ -209,4 +120,35 @@ impl Emulator { } String::from_utf8_lossy(&buf).into_owned() } + + fn detect_base() -> Result { + defer! { disable_raw_mode().ok(); } + enable_raw_mode()?; + + execute!( + LineWriter::new(stderr()), + Print(Mux::csi("\x1b]11;?\x07")), // Request background color + Print(Mux::csi("\x1b[c")), // Request device attributes + )?; + + let resp = futures::executor::block_on(Self::read_until_da1()); + Ok(Self { light: Self::light_bg(&resp).unwrap_or_default(), ..Default::default() }) + } + + fn light_bg(resp: &str) -> Result { + match resp.split_once("]11;rgb:") { + Some((_, s)) if s.len() >= 14 => { + let r = u8::from_str_radix(&s[0..2], 16)? as f32; + let g = u8::from_str_radix(&s[5..7], 16)? as f32; + let b = u8::from_str_radix(&s[10..12], 16)? as f32; + let luma = r * 0.2627 / 256.0 + g * 0.6780 / 256.0 + b * 0.0593 / 256.0; + debug!("Detected background color: {} (luma = {luma:.2})", &s[..14]); + Ok(luma > 0.6) + } + _ => { + warn!("Failed to detect background color: {resp:?}"); + Ok(false) + } + } + } } diff --git a/yazi-adapter/src/lib.rs b/yazi-adapter/src/lib.rs index 8e341ed4..1b177e54 100644 --- a/yazi-adapter/src/lib.rs +++ b/yazi-adapter/src/lib.rs @@ -2,11 +2,16 @@ yazi_macro::mod_pub!(drivers); -yazi_macro::mod_flat!(adapter dimension emulator image info mux); +yazi_macro::mod_flat!(adapter brand dimension emulator image info mux unknown); use yazi_shared::{RoCell, SyncCell, env_exists, in_wsl}; + +pub static EMULATOR: RoCell = RoCell::new(); pub static ADAPTOR: RoCell = RoCell::new(); +// Image state +static SHOWN: SyncCell> = SyncCell::new(None); + // Tmux support pub static TMUX: RoCell = RoCell::new(); static ESCAPE: RoCell<&'static str> = RoCell::new(); @@ -16,10 +21,7 @@ static CLOSE: RoCell<&'static str> = RoCell::new(); // WSL support pub static WSL: RoCell = RoCell::new(); -// Image state -static SHOWN: SyncCell> = SyncCell::new(None); - -pub fn init() { +pub fn init() -> anyhow::Result<()> { // Tmux support TMUX.init(env_exists("TMUX_PANE") && env_exists("TMUX")); ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" }); @@ -38,6 +40,11 @@ pub fn init() { // WSL support WSL.init(in_wsl()); - ADAPTOR.init(Adapter::matches()); + EMULATOR.init(Emulator::detect()); + yazi_config::init_flavor(EMULATOR.light)?; + + ADAPTOR.init(Adapter::matches(*EMULATOR)); ADAPTOR.start(); + + Ok(()) } diff --git a/yazi-adapter/src/unknown.rs b/yazi-adapter/src/unknown.rs new file mode 100644 index 00000000..0a4dd18d --- /dev/null +++ b/yazi-adapter/src/unknown.rs @@ -0,0 +1,20 @@ +use crate::Adapter; + +#[derive(Debug, Clone, Copy, Default)] +pub struct Unknown { + pub kgp: bool, + pub sixel: bool, +} + +impl Unknown { + pub(super) fn adapters(self) -> &'static [Adapter] { + use Adapter as A; + + match (self.kgp, self.sixel) { + (true, true) => &[A::Kgp, A::Sixel], + (true, false) => &[A::Kgp], + (false, true) => &[A::Sixel], + (false, false) => &[], + } + } +} diff --git a/yazi-boot/src/actions/debug.rs b/yazi-boot/src/actions/debug.rs index 43919262..ced554ed 100644 --- a/yazi-boot/src/actions/debug.rs +++ b/yazi-boot/src/actions/debug.rs @@ -17,13 +17,14 @@ impl Actions { writeln!(s, "\nYa")?; writeln!(s, " Version: {}", Self::process_output("ya", "--version"))?; + let emulator = yazi_adapter::Emulator::detect(); writeln!(s, "\nEmulator")?; - writeln!(s, " Emulator.via_env: {:?}", yazi_adapter::Emulator::via_env())?; - writeln!(s, " Emulator.via_csi: {:?}", yazi_adapter::Emulator::via_csi())?; - writeln!(s, " Emulator.detect : {:?}", yazi_adapter::Emulator::detect())?; + writeln!(s, " Brand.from_env : {:?}", yazi_adapter::Brand::from_env())?; + writeln!(s, " Emulator.detect : {:?}", emulator)?; + writeln!(s, " Emulator.detect_full: {:?}", yazi_adapter::Emulator::detect_full())?; writeln!(s, "\nAdapter")?; - writeln!(s, " Adapter.matches: {:?}", yazi_adapter::Adapter::matches())?; + writeln!(s, " Adapter.matches: {:?}", yazi_adapter::Adapter::matches(emulator))?; writeln!(s, "\nDesktop")?; writeln!(s, " XDG_SESSION_TYPE : {:?}", env::var_os("XDG_SESSION_TYPE"))?; diff --git a/yazi-config/preset/flavor-dark.toml b/yazi-config/preset/theme+dark.toml similarity index 100% rename from yazi-config/preset/flavor-dark.toml rename to yazi-config/preset/theme+dark.toml diff --git a/yazi-config/preset/flavor-light.toml b/yazi-config/preset/theme+light.toml similarity index 100% rename from yazi-config/preset/flavor-light.toml rename to yazi-config/preset/theme+light.toml diff --git a/yazi-config/preset/theme.toml b/yazi-config/preset/theme.toml index 1a15aae7..c448aeb8 100644 --- a/yazi-config/preset/theme.toml +++ b/yazi-config/preset/theme.toml @@ -214,16 +214,3 @@ rules = [ ] # : }}} - - -# : Icons {{{ - -[icon] - -globs = [] -dirs = [] -files = [] -exts = [] -conds = [] - -# : }}} diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index b244c63f..5cbf3b53 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -24,22 +24,7 @@ pub static LAYOUT: SyncCell = SyncCell::new(Layout::default()); pub fn init() -> anyhow::Result<()> { if let Err(e) = try_init(true) { - eprintln!("{e}"); - if let Some(src) = e.source() { - eprintln!("\nCaused by:\n{src}"); - } - - use crossterm::style::{Attribute, Print, SetAttributes}; - crossterm::execute!( - std::io::stderr(), - SetAttributes(Attribute::Reverse.into()), - SetAttributes(Attribute::Bold.into()), - Print("Press to continue with preset settings..."), - SetAttributes(Attribute::Reset.into()), - Print("\n"), - )?; - - std::io::stdin().read_line(&mut String::new())?; + wait_for_key(e)?; try_init(false)?; } @@ -78,13 +63,29 @@ Please change `create_title = "Create:"` to `create_title = ["Create:", "Create Ok(()) } +pub fn init_flavor(light: bool) -> anyhow::Result<()> { + let mut flavor_toml = Preset::flavor(light, true); + if let Err(e) = flavor_toml { + wait_for_key(e)?; + flavor_toml = Preset::flavor(light, false); + } + + let mut theme: theme::Theme = <_>::from_str(&flavor_toml.unwrap())?; + theme.manager.syntect_theme = theme + .flavor + .syntect_path(light) + .unwrap_or_else(|| yazi_shared::fs::expand_path(&theme.manager.syntect_theme)); + + THEME.init(theme); + Ok(()) +} + fn try_init(merge: bool) -> anyhow::Result<()> { - let (yazi_toml, keymap_toml, theme_toml) = if merge { + let (yazi_toml, keymap_toml) = if merge { let p = Xdg::config_dir(); - (Preset::yazi(&p)?, Preset::keymap(&p)?, Preset::theme(&p)?) + (Preset::yazi(&p)?, Preset::keymap(&p)?) } else { - use yazi_macro::config_preset as preset; - (preset!("yazi"), preset!("keymap"), preset!("theme")) + (yazi_macro::config_preset!("yazi"), yazi_macro::config_preset!("keymap")) }; let keymap = <_>::from_str(&keymap_toml)?; @@ -93,7 +94,6 @@ fn try_init(merge: bool) -> anyhow::Result<()> { let plugin = <_>::from_str(&yazi_toml)?; let preview = <_>::from_str(&yazi_toml)?; let tasks = <_>::from_str(&yazi_toml)?; - let theme = <_>::from_str(&theme_toml)?; let input = <_>::from_str(&yazi_toml)?; let confirm = <_>::from_str(&yazi_toml)?; let pick = <_>::from_str(&yazi_toml)?; @@ -105,7 +105,6 @@ fn try_init(merge: bool) -> anyhow::Result<()> { PLUGIN.init(plugin); PREVIEW.init(preview); TASKS.init(tasks); - THEME.init(theme); INPUT.init(input); CONFIRM.init(confirm); PICK.init(pick); @@ -113,3 +112,23 @@ fn try_init(merge: bool) -> anyhow::Result<()> { Ok(()) } + +fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> { + eprintln!("{e}"); + if let Some(src) = e.source() { + eprintln!("\nCaused by:\n{src}"); + } + + use crossterm::style::{Attribute, Print, SetAttributes}; + crossterm::execute!( + std::io::stderr(), + SetAttributes(Attribute::Reverse.into()), + SetAttributes(Attribute::Bold.into()), + Print("Press to continue with preset settings..."), + SetAttributes(Attribute::Reset.into()), + Print("\n"), + )?; + + std::io::stdin().read_line(&mut String::new())?; + Ok(()) +} diff --git a/yazi-config/src/preset.rs b/yazi-config/src/preset.rs index e3bb0c5c..3ea85edd 100644 --- a/yazi-config/src/preset.rs +++ b/yazi-config/src/preset.rs @@ -1,8 +1,8 @@ -use std::{borrow::Cow, path::{Path, PathBuf}}; +use std::{borrow::Cow, path::{Path, PathBuf}, str::FromStr}; use anyhow::{Context, Result}; use toml::{Table, Value}; -use yazi_macro::config_preset as preset; +use yazi_shared::Xdg; use crate::theme::Flavor; @@ -10,26 +10,26 @@ pub(crate) struct Preset; impl Preset { pub(crate) fn yazi(p: &Path) -> Result> { - Self::merge_path(p.join("yazi.toml"), preset!("yazi")) + Self::merge_path(p.join("yazi.toml"), yazi_macro::config_preset!("yazi")) } pub(crate) fn keymap(p: &Path) -> Result> { - Self::merge_path(p.join("keymap.toml"), preset!("keymap")) + Self::merge_path(p.join("keymap.toml"), yazi_macro::config_preset!("keymap")) } - pub(crate) fn theme(p: &Path) -> Result> { - let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else { - return Ok(preset!("theme")); - }; - let Some(use_) = Flavor::parse_use(&user) else { - return Self::merge_str(&user, &preset!("theme")); + pub(crate) fn flavor(light: bool, merge: bool) -> Result> { + let theme = if merge { + std::fs::read_to_string(Xdg::config_dir().join("theme.toml")).unwrap_or_default() + } else { + Default::default() }; - let p = p.join(format!("flavors/{use_}.yazi/flavor.toml")); - let flavor = - std::fs::read_to_string(&p).with_context(|| format!("failed to load flavor {p:?}"))?; + let flavor = Flavor::from_str(&theme)?; - Self::merge_str(&user, &Self::merge_str(&flavor, &preset!("theme"))?) + let preset = + if light { yazi_macro::theme_preset!("light") } else { yazi_macro::theme_preset!("dark") }; + + Self::merge_str(&theme, &Self::merge_str(&flavor.read(light)?, &preset)?) } #[inline] diff --git a/yazi-config/src/theme/flavor.rs b/yazi-config/src/theme/flavor.rs index 7827c203..e621a393 100644 --- a/yazi-config/src/theme/flavor.rs +++ b/yazi-config/src/theme/flavor.rs @@ -1,23 +1,50 @@ -use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, path::PathBuf, str::FromStr}; -#[derive(Deserialize, Serialize)] +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use yazi_shared::Xdg; + +#[derive(Default, Deserialize, Serialize)] pub struct Flavor { - #[serde(rename = "use")] - pub use_: String, + #[serde(default)] + pub dark: String, + #[serde(default)] + pub light: String, +} + +impl FromStr for Flavor { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + #[derive(Deserialize)] + struct Outer { + #[serde(default)] + flavor: Flavor, + } + + Ok( + toml::from_str::(s) + .context("Failed to parse the [flavor] section in your theme.toml")? + .flavor, + ) + } } impl Flavor { - pub fn parse_use(s: &str) -> Option { - #[derive(Deserialize)] - struct Outer { - flavor: Inner, - } - #[derive(Deserialize)] - struct Inner { - #[serde(rename = "use")] - pub use_: String, - } + pub(crate) fn read(&self, light: bool) -> Result> { + Ok(match if light { self.light.as_str() } else { self.dark.as_str() } { + "" => Cow::Borrowed(""), + name => { + let p = Xdg::config_dir().join(format!("flavors/{name}.yazi/flavor.toml")); + std::fs::read_to_string(&p).with_context(|| format!("Failed to load flavor {p:?}"))?.into() + } + }) + } - toml::from_str::(s).ok().map(|o| o.flavor.use_).filter(|s| !s.is_empty()) + pub(crate) fn syntect_path(&self, light: bool) -> Option { + match if light { self.light.as_str() } else { self.dark.as_str() } { + "" => None, + name => Some(Xdg::config_dir().join(format!("flavors/{name}.yazi/tmtheme.xml"))), + } } } diff --git a/yazi-config/src/theme/theme.rs b/yazi-config/src/theme/theme.rs index 7712b102..6220dd6a 100644 --- a/yazi-config/src/theme/theme.rs +++ b/yazi-config/src/theme/theme.rs @@ -3,7 +3,7 @@ use std::{path::PathBuf, str::FromStr}; use anyhow::Context; use serde::{Deserialize, Serialize}; use validator::Validate; -use yazi_shared::{Xdg, fs::expand_path, theme::Style}; +use yazi_shared::theme::Style; use super::{Filetype, Flavor, Icons}; @@ -32,17 +32,10 @@ impl FromStr for Theme { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - let mut theme: Self = toml::from_str(s).context("Failed to parse your yazi.toml")?; + let theme: Self = toml::from_str(s).context("Failed to parse your theme.toml")?; theme.manager.validate()?; theme.which.validate()?; - if theme.flavor.use_.is_empty() { - theme.manager.syntect_theme = expand_path(&theme.manager.syntect_theme); - } else { - theme.manager.syntect_theme = - Xdg::config_dir().join(format!("flavors/{}.yazi/tmtheme.xml", theme.flavor.use_)); - } - Ok(theme) } } diff --git a/yazi-config/src/which/which.rs b/yazi-config/src/which/which.rs index 8a488d3f..d47f73e1 100644 --- a/yazi-config/src/which/which.rs +++ b/yazi-config/src/which/which.rs @@ -24,8 +24,8 @@ impl FromStr for Which { which: Which, } - let outer = toml::from_str::(s) - .context("Failed to parse the [which] section in your yazi.toml")?; + let outer: Outer = + toml::from_str(s).context("Failed to parse the [which] section in your yazi.toml")?; Ok(outer.which) } diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index ef71acee..8379d72d 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -20,7 +20,7 @@ async fn main() -> anyhow::Result<()> { yazi_config::init()?; - yazi_adapter::init(); + yazi_adapter::init()?; yazi_boot::init(); diff --git a/yazi-macro/src/asset.rs b/yazi-macro/src/asset.rs index fc33384f..a46ccfa1 100644 --- a/yazi-macro/src/asset.rs +++ b/yazi-macro/src/asset.rs @@ -3,14 +3,14 @@ macro_rules! config_preset { ($name:literal) => {{ #[cfg(debug_assertions)] { - std::borrow::Cow::Owned( + std::borrow::Cow::from( std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/preset/", $name, ".toml")) .expect(concat!("Failed to read 'yazi-config/preset/", $name, ".toml'")), ) } #[cfg(not(debug_assertions))] { - std::borrow::Cow::Borrowed(include_str!(concat!( + std::borrow::Cow::from(include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/preset/", $name, @@ -37,3 +37,28 @@ macro_rules! plugin_preset { } }}; } + +#[macro_export] +macro_rules! theme_preset { + ($name:literal) => {{ + #[cfg(debug_assertions)] + { + let append = std::fs::read_to_string(concat!( + env!("CARGO_MANIFEST_DIR"), + "/preset/theme+", + $name, + ".toml" + )) + .expect(concat!("Failed to read 'yazi-config/preset/theme+", $name, ".toml'")); + std::borrow::Cow::from(format!("{}\n{append}", &$crate::config_preset!("theme"))) + } + #[cfg(not(debug_assertions))] + { + std::borrow::Cow::from(concat!( + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/preset/theme.toml")), + "\n", + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/preset/theme+", $name, ".toml")) + )) + } + }}; +} diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index 64a9360c..f30cc0fc 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -57,7 +57,7 @@ impl Loader { let p = BOOT.plugin_dir.join(format!("{name}.yazi/init.lua")); let chunk = - fs::read(&p).await.with_context(|| format!("failed to load plugin from {p:?}"))?.into(); + fs::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into(); self.cache.write().insert(name.to_owned(), chunk); Ok(()) diff --git a/yazi-shared/src/either.rs b/yazi-shared/src/either.rs new file mode 100644 index 00000000..1a6a3e58 --- /dev/null +++ b/yazi-shared/src/either.rs @@ -0,0 +1,49 @@ +#[derive(Clone, Copy, Debug)] +pub enum Either { + Left(L), + Right(R), +} + +impl Either { + pub fn left(&self) -> Option<&L> { + match self { + Either::Left(l) => Some(l), + _ => None, + } + } + + pub fn right(&self) -> Option<&R> { + match self { + Either::Right(r) => Some(r), + _ => None, + } + } + + pub fn left_mut(&mut self) -> Option<&mut L> { + match self { + Either::Left(l) => Some(l), + _ => None, + } + } + + pub fn right_mut(&mut self) -> Option<&mut R> { + match self { + Either::Right(r) => Some(r), + _ => None, + } + } + + pub fn into_left(self) -> Option { + match self { + Either::Left(l) => Some(l), + _ => None, + } + } + + pub fn into_right(self) -> Option { + match self { + Either::Right(r) => Some(r), + _ => None, + } + } +} diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 1dd01b9d..24bb6f8d 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -2,7 +2,7 @@ yazi_macro::mod_pub!(errors event fs shell theme translit); -yazi_macro::mod_flat!(chars condition debounce env id layer natsort number os rand ro_cell sync_cell terminal throttle time xdg); +yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time xdg); pub fn init() { #[cfg(unix)]