diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index a4e315be..1c0b17e9 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -12,21 +12,14 @@ body: - Linux Wayland - macOS - Windows + - Windows WSL validations: required: true - type: input id: terminal attributes: label: What terminal are you running Yazi in? - placeholder: "ex: Kitty v0.30.1" - validations: - required: true - - type: input - id: version - attributes: - label: Yazi version - description: Please do a `yazi -V` and paste the output here. - placeholder: "ex: yazi 0.1.5 (3867c29 2023-11-25)" + placeholder: "ex: kitty v0.32.2" validations: required: true - type: dropdown @@ -38,6 +31,18 @@ body: - Not tried, and I'll explain why below validations: required: true + - type: textarea + id: debug + attributes: + label: "`yazi --debug` output" + description: Please do a `yazi --debug` and paste the output here. + value: | + + ```sh + + ``` + validations: + required: true - type: textarea id: description attributes: diff --git a/Cargo.lock b/Cargo.lock index d9d53dd8..5c03f312 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2716,6 +2716,7 @@ dependencies = [ "clap_complete_nushell", "serde", "vergen", + "yazi-adaptor", "yazi-config", "yazi-shared", ] diff --git a/yazi-adaptor/src/adaptor.rs b/yazi-adaptor/src/adaptor.rs index d24c9abb..0cece50a 100644 --- a/yazi-adaptor/src/adaptor.rs +++ b/yazi-adaptor/src/adaptor.rs @@ -1,13 +1,12 @@ -use std::{env, fmt::Display, io::{Read, Write}, path::Path, sync::Arc}; +use std::{env, fmt::Display, path::Path, sync::Arc}; -use anyhow::{anyhow, Result}; -use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use anyhow::Result; use ratatui::layout::Rect; use tracing::warn; use yazi_shared::{env_exists, term::Term}; use super::{Iterm2, Kitty, KittyOld}; -use crate::{ueberzug::Ueberzug, Sixel, SHOWN, TMUX}; +use crate::{ueberzug::Ueberzug, Emulator, Sixel, SHOWN, TMUX}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Adaptor { @@ -22,82 +21,73 @@ pub enum Adaptor { Chafa, } -#[derive(Clone)] -enum Emulator { - Unknown(Vec), - Kitty, - Konsole, - Iterm2, - WezTerm, - Foot, - Ghostty, - BlackBox, - VSCode, - Tabby, - Hyper, - Mintty, - Neovim, +impl Display for Adaptor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Kitty => write!(f, "kitty"), + Self::KittyOld => write!(f, "kitty"), + Self::Iterm2 => write!(f, "iterm2"), + Self::Sixel => write!(f, "sixel"), + Self::X11 => write!(f, "x11"), + Self::Wayland => write!(f, "wayland"), + Self::Chafa => write!(f, "chafa"), + } + } } impl Adaptor { - fn emulator() -> Emulator { - if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") { - return Emulator::Neovim; + pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { + match self { + Self::Kitty => Kitty::image_show(path, rect).await, + Self::KittyOld => KittyOld::image_show(path, rect).await, + Self::Iterm2 => Iterm2::image_show(path, rect).await, + Self::Sixel => Sixel::image_show(path, rect).await, + _ => Ueberzug::image_show(path, rect).await, } - - let vars = [ - ("KITTY_WINDOW_ID", Emulator::Kitty), - ("KONSOLE_VERSION", Emulator::Konsole), - ("ITERM_SESSION_ID", Emulator::Iterm2), - ("WEZTERM_EXECUTABLE", Emulator::WezTerm), - ("GHOSTTY_RESOURCES_DIR", Emulator::Ghostty), - ("VSCODE_INJECTION", Emulator::VSCode), - ("TABBY_CONFIG_DIRECTORY", Emulator::Tabby), - ]; - match vars.into_iter().find(|v| env_exists(v.0)) { - Some(var) => return var.1, - None => warn!("[Adaptor] No special environment variables detected"), - } - - let (term, program) = Self::via_env(); - match program.as_str() { - "iTerm.app" => return Emulator::Iterm2, - "WezTerm" => return Emulator::WezTerm, - "ghostty" => return Emulator::Ghostty, - "BlackBox" => return Emulator::BlackBox, - "vscode" => return Emulator::VSCode, - "Tabby" => return Emulator::Tabby, - "Hyper" => return Emulator::Hyper, - "mintty" => return Emulator::Mintty, - _ => warn!("[Adaptor] Unknown TERM_PROGRAM: {program}"), - } - match term.as_str() { - "xterm-kitty" => return Emulator::Kitty, - "foot" => return Emulator::Foot, - "foot-extra" => return Emulator::Foot, - "xterm-ghostty" => return Emulator::Ghostty, - _ => warn!("[Adaptor] Unknown TERM: {term}"), - } - - Self::via_csi().unwrap_or(Emulator::Unknown(vec![])) } - pub(super) fn detect() -> Self { - let mut protocols = match Self::emulator() { - Emulator::Unknown(adapters) => adapters, - Emulator::Kitty => vec![Self::Kitty], - Emulator::Konsole => vec![Self::KittyOld, Self::Iterm2, Self::Sixel], - Emulator::Iterm2 => vec![Self::Iterm2, Self::Sixel], - Emulator::WezTerm => vec![Self::Iterm2, Self::Sixel], - Emulator::Foot => vec![Self::Sixel], - Emulator::Ghostty => vec![Self::KittyOld], - Emulator::BlackBox => vec![Self::Sixel], - Emulator::VSCode => vec![Self::Iterm2, Self::Sixel], - Emulator::Tabby => vec![Self::Iterm2, Self::Sixel], - Emulator::Hyper => vec![Self::Iterm2, Self::Sixel], - Emulator::Mintty => vec![Self::Iterm2], - Emulator::Neovim => vec![], - }; + pub fn image_hide(self) -> Result<()> { + if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) } + } + + pub fn image_erase(self, rect: Rect) -> Result<()> { + match self { + Self::Kitty => Kitty::image_erase(rect), + Self::Iterm2 => Iterm2::image_erase(rect), + Self::KittyOld => KittyOld::image_erase(), + Self::Sixel => Sixel::image_erase(rect), + _ => Ueberzug::image_erase(rect), + } + } + + #[inline] + pub fn shown_load(self) -> Option { SHOWN.load_full().map(|r| *r) } + + pub(super) fn start(self) { Ueberzug::start(self); } + + #[inline] + pub(super) fn shown_store(rect: Rect, size: (u32, u32)) { + SHOWN.store(Some(Arc::new( + Term::ratio() + .map(|(r1, r2)| Rect { + x: rect.x, + y: rect.y, + width: (size.0 as f64 / r1).ceil() as u16, + height: (size.1 as f64 / r2).ceil() as u16, + }) + .unwrap_or(rect), + ))); + } + + #[inline] + pub(super) fn needs_ueberzug(self) -> bool { + !matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel) + } +} + +impl Adaptor { + pub fn matches() -> Self { + let mut protocols = Emulator::detect().adapters(); #[cfg(windows)] protocols.retain(|p| *p == Self::Iterm2); @@ -129,134 +119,4 @@ impl Adaptor { warn!("[Adaptor] Falling back to chafa"); Self::Chafa } - - fn via_env() -> (String, String) { - fn tmux_env(name: &str) -> Result { - let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?; - - String::from_utf8(output.stdout)? - .trim() - .strip_prefix(&format!("{name}=")) - .map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string())) - } - - let mut term = env::var("TERM").unwrap_or_default(); - let mut program = env::var("TERM_PROGRAM").unwrap_or_default(); - - if *TMUX { - term = tmux_env("TERM").unwrap_or(term); - program = tmux_env("TERM_PROGRAM").unwrap_or(program); - } - - (term, program) - } - - fn via_csi() -> Result { - enable_raw_mode()?; - std::io::stdout().write_all(b"\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c")?; - std::io::stdout().flush()?; - - let mut stdin = std::io::stdin().lock(); - let mut buf = String::with_capacity(200); - loop { - let mut c = [0; 1]; - if stdin.read(&mut c)? == 0 { - break; - } - if c[0] == b'c' && buf.contains("\x1b[?") { - break; - } - buf.push(c[0] as char); - } - - disable_raw_mode().ok(); - let names = [ - ("kitty", Emulator::Kitty), - ("Konsole", Emulator::Konsole), - ("iTerm2", Emulator::Iterm2), - ("WezTerm", Emulator::WezTerm), - ("foot", Emulator::Foot), - ("ghostty", Emulator::Ghostty), - ]; - - for (name, emulator) in names.iter() { - if buf.contains(name) { - return Ok(emulator.clone()); - } - } - - let mut adapters = Vec::with_capacity(2); - if buf.contains("\x1b_Gi=31;OK") { - adapters.push(Adaptor::KittyOld); - } - if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| buf.contains(s)) { - adapters.push(Adaptor::Sixel); - } - - Ok(Emulator::Unknown(adapters)) - } -} - -impl Display for Adaptor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Kitty => write!(f, "kitty"), - Self::KittyOld => write!(f, "kitty"), - Self::Iterm2 => write!(f, "iterm2"), - Self::Sixel => write!(f, "sixel"), - Self::X11 => write!(f, "x11"), - Self::Wayland => write!(f, "wayland"), - Self::Chafa => write!(f, "chafa"), - } - } -} - -impl Adaptor { - pub(super) fn start(self) { Ueberzug::start(self); } - - pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { - match self { - Self::Kitty => Kitty::image_show(path, rect).await, - Self::KittyOld => KittyOld::image_show(path, rect).await, - Self::Iterm2 => Iterm2::image_show(path, rect).await, - Self::Sixel => Sixel::image_show(path, rect).await, - _ => Ueberzug::image_show(path, rect).await, - } - } - - pub fn image_hide(self) -> Result<()> { - if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) } - } - - pub fn image_erase(self, rect: Rect) -> Result<()> { - match self { - Self::Kitty => Kitty::image_erase(rect), - Self::Iterm2 => Iterm2::image_erase(rect), - Self::KittyOld => KittyOld::image_erase(), - Self::Sixel => Sixel::image_erase(rect), - _ => Ueberzug::image_erase(rect), - } - } - - #[inline] - pub fn shown_load(self) -> Option { SHOWN.load_full().map(|r| *r) } - - #[inline] - pub(super) fn shown_store(rect: Rect, size: (u32, u32)) { - SHOWN.store(Some(Arc::new( - Term::ratio() - .map(|(r1, r2)| Rect { - x: rect.x, - y: rect.y, - width: (size.0 as f64 / r1).ceil() as u16, - height: (size.1 as f64 / r2).ceil() as u16, - }) - .unwrap_or(rect), - ))); - } - - #[inline] - pub(super) fn needs_ueberzug(self) -> bool { - !matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel) - } } diff --git a/yazi-adaptor/src/emulator.rs b/yazi-adaptor/src/emulator.rs new file mode 100644 index 00000000..30702fff --- /dev/null +++ b/yazi-adaptor/src/emulator.rs @@ -0,0 +1,155 @@ +use std::{env, io::{Read, Write}}; + +use anyhow::{anyhow, Result}; +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use tracing::warn; +use yazi_shared::env_exists; + +use crate::{Adaptor, TMUX}; + +#[derive(Clone, Debug)] +pub enum Emulator { + Unknown(Vec), + Kitty, + Konsole, + Iterm2, + WezTerm, + Foot, + Ghostty, + BlackBox, + VSCode, + Tabby, + Hyper, + Mintty, + Neovim, +} + +impl Emulator { + pub fn adapters(self) -> Vec { + match self { + Self::Unknown(adapters) => adapters, + Self::Kitty => vec![Adaptor::Kitty], + Self::Konsole => vec![Adaptor::KittyOld, Adaptor::Iterm2, Adaptor::Sixel], + Self::Iterm2 => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::WezTerm => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Foot => vec![Adaptor::Sixel], + Self::Ghostty => vec![Adaptor::KittyOld], + Self::BlackBox => vec![Adaptor::Sixel], + Self::VSCode => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Tabby => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Hyper => vec![Adaptor::Iterm2, Adaptor::Sixel], + Self::Mintty => vec![Adaptor::Iterm2], + Self::Neovim => vec![], + } + } +} + +impl Emulator { + pub fn detect() -> Self { + if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") { + return Self::Neovim; + } + + 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), + ("VSCODE_INJECTION", Self::VSCode), + ("TABBY_CONFIG_DIRECTORY", Self::Tabby), + ]; + match vars.into_iter().find(|v| env_exists(v.0)) { + Some(var) => return var.1, + None => warn!("[Adaptor] 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, + "BlackBox" => return Self::BlackBox, + "vscode" => return Self::VSCode, + "Tabby" => return Self::Tabby, + "Hyper" => return Self::Hyper, + "mintty" => return Self::Mintty, + _ => warn!("[Adaptor] 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, + _ => warn!("[Adaptor] Unknown TERM: {term}"), + } + + Self::via_csi().unwrap_or(Self::Unknown(vec![])) + } + + pub fn via_env() -> (String, String) { + fn tmux_env(name: &str) -> Result { + let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?; + + String::from_utf8(output.stdout)? + .trim() + .strip_prefix(&format!("{name}=")) + .map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string())) + } + + let mut term = env::var("TERM").unwrap_or_default(); + let mut program = env::var("TERM_PROGRAM").unwrap_or_default(); + + if *TMUX { + term = tmux_env("TERM").unwrap_or(term); + program = tmux_env("TERM_PROGRAM").unwrap_or(program); + } + + (term, program) + } + + pub fn via_csi() -> Result { + enable_raw_mode()?; + std::io::stdout().write_all(b"\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c")?; + std::io::stdout().flush()?; + + let mut stdin = std::io::stdin().lock(); + let mut buf = String::with_capacity(200); + loop { + let mut c = [0; 1]; + if stdin.read(&mut c)? == 0 { + break; + } + if c[0] == b'c' && buf.contains("\x1b[?") { + break; + } + buf.push(c[0] as char); + } + + disable_raw_mode().ok(); + let names = [ + ("kitty", Self::Kitty), + ("Konsole", Self::Konsole), + ("iTerm2", Self::Iterm2), + ("WezTerm", Self::WezTerm), + ("foot", Self::Foot), + ("ghostty", Self::Ghostty), + ]; + + for (name, emulator) in names.iter() { + if buf.contains(name) { + return Ok(emulator.clone()); + } + } + + let mut adapters = Vec::with_capacity(2); + if buf.contains("\x1b_Gi=31;OK") { + adapters.push(Adaptor::KittyOld); + } + if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| buf.contains(s)) { + adapters.push(Adaptor::Sixel); + } + + Ok(Self::Unknown(adapters)) + } +} diff --git a/yazi-adaptor/src/lib.rs b/yazi-adaptor/src/lib.rs index b4d52f4a..700e944e 100644 --- a/yazi-adaptor/src/lib.rs +++ b/yazi-adaptor/src/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::unit_arg)] mod adaptor; +mod emulator; mod image; mod iterm2; mod kitty; @@ -8,7 +9,8 @@ mod kitty_old; mod sixel; mod ueberzug; -use adaptor::*; +pub use adaptor::*; +pub use emulator::*; use iterm2::*; use kitty::*; use kitty_old::*; @@ -20,7 +22,7 @@ pub use crate::image::*; pub static ADAPTOR: RoCell = RoCell::new(); // Tmux support -static TMUX: RoCell = RoCell::new(); +pub static TMUX: RoCell = RoCell::new(); static ESCAPE: RoCell<&'static str> = RoCell::new(); static START: RoCell<&'static str> = RoCell::new(); static CLOSE: RoCell<&'static str> = RoCell::new(); @@ -36,7 +38,7 @@ pub fn init() { SHOWN.with(Default::default); - ADAPTOR.init(Adaptor::detect()); + ADAPTOR.init(Adaptor::matches()); ADAPTOR.start(); if *TMUX { diff --git a/yazi-adaptor/src/ueberzug.rs b/yazi-adaptor/src/ueberzug.rs index 35d8c232..c2f9a6c1 100644 --- a/yazi-adaptor/src/ueberzug.rs +++ b/yazi-adaptor/src/ueberzug.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Result}; use imagesize::ImageSize; use ratatui::layout::Rect; use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}}; -use tracing::debug; +use tracing::{debug, warn}; use yazi_config::PREVIEW; use yazi_shared::RoCell; @@ -71,14 +71,17 @@ impl Ueberzug { } fn create_demon(adaptor: Adaptor) -> Result { - Ok( - Command::new("ueberzug") - .args(["layer", "-so", &adaptor.to_string()]) - .kill_on_drop(true) - .stdin(Stdio::piped()) - .stderr(Stdio::null()) - .spawn()?, - ) + let result = Command::new("ueberzug") + .args(["layer", "-so", &adaptor.to_string()]) + .kill_on_drop(true) + .stdin(Stdio::piped()) + .stderr(Stdio::null()) + .spawn(); + + if let Err(ref e) = result { + warn!("ueberzug spawning failed: {}", e); + } + Ok(result?) } fn adjust_rect(mut rect: Rect) -> Rect { diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 42c5b4c4..dfe8cbc0 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -9,8 +9,9 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "0.2.4" } -yazi-shared = { path = "../yazi-shared", version = "0.2.4" } +yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" } +yazi-config = { path = "../yazi-config", version = "0.2.4" } +yazi-shared = { path = "../yazi-shared", version = "0.2.4" } # External dependencies clap = { version = "^4", features = [ "derive" ] } diff --git a/yazi-boot/src/args.rs b/yazi-boot/src/args.rs index 08c4a11a..8c9181da 100644 --- a/yazi-boot/src/args.rs +++ b/yazi-boot/src/args.rs @@ -20,6 +20,10 @@ pub struct Args { #[arg(long, action)] pub clear_cache: bool, + /// Print debug information + #[arg(long, action)] + pub debug: bool, + /// Print version #[arg(short = 'V', long)] pub version: bool, diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index 65fee0f0..6380445c 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsString, path::{Path, PathBuf}, process}; +use std::{env, ffi::OsString, path::{Path, PathBuf}, process}; use clap::Parser; use serde::Serialize; @@ -32,6 +32,63 @@ impl Boot { (parent.unwrap().to_owned(), Some(entry.file_name().unwrap().to_owned())) } + + fn action_version() { + println!( + "yazi {} ({} {})", + env!("CARGO_PKG_VERSION"), + env!("VERGEN_GIT_SHA"), + env!("VERGEN_BUILD_DATE") + ); + } + + fn action_debug() { + print!("Yazi\n "); + Self::action_version(); + + println!("\nEnvironment"); + println!( + " OS: {}-{} ({})", + std::env::consts::OS, + std::env::consts::ARCH, + std::env::consts::FAMILY + ); + println!(" Debug: {}", cfg!(debug_assertions)); + + println!("\nEmulator"); + println!(" Emulator.via_env: {:?}", yazi_adaptor::Emulator::via_env()); + println!(" Emulator.via_csi: {:?}", yazi_adaptor::Emulator::via_csi()); + println!(" Emulator.detect: {:?}", yazi_adaptor::Emulator::detect()); + + println!("\nAdaptor"); + println!(" Adaptor.matches: {:?}", yazi_adaptor::Adaptor::matches()); + + println!("\ntmux"); + println!(" TMUX: {:?}", *yazi_adaptor::TMUX); + + println!("\nZellij"); + println!(" ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME")); + + println!("\nDesktop"); + println!(" XDG_SESSION_TYPE: {:?}", env::var_os("XDG_SESSION_TYPE")); + println!(" WAYLAND_DISPLAY: {:?}", env::var_os("WAYLAND_DISPLAY")); + println!(" DISPLAY: {:?}", env::var_os("DISPLAY")); + + println!("\nUeberzug"); + println!(" Version: {:?}", std::process::Command::new("ueberzug").arg("--version").output()); + + println!("\nWSL"); + println!( + " /proc/sys/fs/binfmt_misc/WSLInterop: {:?}", + std::fs::symlink_metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok() + ); + + println!("\n\n--------------------------------------------------"); + println!( + "When reporting a bug, please also upload the `yazi.log` log file - only upload the most recent content by time." + ); + println!("You can find it in the {:?} directory.", Xdg::state_dir()); + } } impl Default for Boot { @@ -58,13 +115,13 @@ impl Default for Args { fn default() -> Self { let args = Self::parse(); + if args.debug { + Boot::action_debug(); + process::exit(0); + } + if args.version { - println!( - "yazi {} ({} {})", - env!("CARGO_PKG_VERSION"), - env!("VERGEN_GIT_SHA"), - env!("VERGEN_BUILD_DATE") - ); + Boot::action_version(); process::exit(0); }