use std::{env, io::{stderr, LineWriter}}; use anyhow::{anyhow, Result}; use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}}; use scopeguard::defer; use tracing::warn; use yazi_shared::{env_exists, term::Term}; use crate::{Adaptor, CLOSE, ESCAPE, START, TMUX}; #[derive(Clone, Debug)] pub enum Emulator { Unknown(Vec), Kitty, Konsole, Iterm2, WezTerm, Foot, Ghostty, BlackBox, VSCode, Tabby, Hyper, Mintty, Neovim, Apple, Urxvt, } impl Emulator { pub fn adapters(self) -> Vec { match self { Self::Unknown(adapters) => adapters, Self::Kitty => vec![Adaptor::Kitty], Self::Konsole => vec![Adaptor::Iterm2, Adaptor::KittyOld, 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![], Self::Apple => vec![], Self::Urxvt => 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, "Apple_Terminal" => return Self::Apple, _ => 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, "rxvt-unicode-256color" => return Self::Urxvt, _ => 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 { defer! { disable_raw_mode().ok(); } enable_raw_mode()?; execute!( LineWriter::new(stderr()), SavePosition, Print(format!( "{}[>q{}_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA{}\\{}[c{}", START, ESCAPE, ESCAPE, ESCAPE, CLOSE )), RestorePosition )?; let resp = futures::executor::block_on(Term::read_until_da1())?; 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 resp.contains(name) { return Ok(emulator.clone()); } } let mut adapters = Vec::with_capacity(2); if resp.contains("\x1b_Gi=31;OK") { adapters.push(Adaptor::KittyOld); } if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| resp.contains(s)) { adapters.push(Adaptor::Sixel); } Ok(Self::Unknown(adapters)) } pub fn move_lock((x, y): (u16, u16), cb: F) -> Result where F: FnOnce(&mut std::io::BufWriter) -> Result, { use std::{io::Write, thread, time::Duration}; use crossterm::{cursor::{Hide, MoveTo, RestorePosition, SavePosition, Show}, queue}; let mut buf = std::io::BufWriter::new(stderr().lock()); // I really don't want to add this, // But tmux and ConPTY sometimes cause the cursor position to get out of sync. if *TMUX || cfg!(windows) { execute!(buf, SavePosition, MoveTo(x, y), Show)?; execute!(buf, MoveTo(x, y), Show)?; execute!(buf, MoveTo(x, y), Show)?; thread::sleep(Duration::from_millis(1)); } else { queue!(buf, SavePosition, MoveTo(x, y))?; } let result = cb(&mut buf); if *TMUX || cfg!(windows) { queue!(buf, Hide, RestorePosition)?; } else { queue!(buf, RestorePosition)?; } buf.flush()?; result } }