mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: support local tmux image preview over SSH (#2229)
This commit is contained in:
parent
b4ca02f703
commit
245fb030df
14 changed files with 133 additions and 69 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -334,9 +334,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.9"
|
||||
version = "1.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
|
||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
|
@ -2276,9 +2276,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.135"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
|
||||
checksum = "336a0c23cf42a38d9eaa7cd22c7040d04e1228a19a933890805ffd00a16437d2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
|
@ -2941,9 +2941,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ ratatui = { version = "0.29.0", features = [ "unstable-rendered-line
|
|||
regex = "1.11.1"
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.217", features = [ "derive" ] }
|
||||
serde_json = "1.0.135"
|
||||
serde_json = "1.0.136"
|
||||
shell-words = "1.1.0"
|
||||
tokio = { version = "1.43.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ impl Adapter {
|
|||
protocols.retain(|p| *p == Self::Iip);
|
||||
if env_exists("ZELLIJ_SESSION_NAME") {
|
||||
protocols.retain(|p| *p == Self::Sixel);
|
||||
} else if *TMUX != 0 {
|
||||
} else if *TMUX {
|
||||
protocols.retain(|p| *p != Self::KgpOld);
|
||||
}
|
||||
if let Some(p) = protocols.first() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{io::{LineWriter, stderr}, time::Duration};
|
||||
use std::{io::{LineWriter, Read, stderr}, time::Duration};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
|
|
@ -84,7 +84,7 @@ impl Emulator {
|
|||
|
||||
// I really don't want to add this,
|
||||
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
|
||||
if *TMUX != 0 || cfg!(windows) {
|
||||
if *TMUX || cfg!(windows) {
|
||||
execute!(buf, SavePosition, MoveTo(x, y), Show)?;
|
||||
execute!(buf, MoveTo(x, y), Show)?;
|
||||
execute!(buf, MoveTo(x, y), Show)?;
|
||||
|
|
@ -94,7 +94,7 @@ impl Emulator {
|
|||
}
|
||||
|
||||
let result = cb(&mut buf);
|
||||
if *TMUX != 0 || cfg!(windows) {
|
||||
if *TMUX || cfg!(windows) {
|
||||
queue!(buf, Hide, RestorePosition)?;
|
||||
} else {
|
||||
queue!(buf, RestorePosition)?;
|
||||
|
|
@ -124,10 +124,16 @@ impl Emulator {
|
|||
Ok(())
|
||||
};
|
||||
|
||||
match timeout(Duration::from_secs(10), read).await {
|
||||
Err(e) => error!("read_until_da1 timed out: {buf:?}, error: {e:?}"),
|
||||
Ok(Err(e)) => error!("read_until_da1 failed: {buf:?}, error: {e:?}"),
|
||||
match timeout(Duration::from_secs(5), read).await {
|
||||
Ok(Ok(())) => debug!("read_until_da1: {buf:?}"),
|
||||
Err(e) => {
|
||||
error!("read_until_da1 timed out: {buf:?}, error: {e:?}");
|
||||
Self::error_to_user().ok();
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("read_until_da1 failed: {buf:?}, error: {e:?}");
|
||||
Self::error_to_user().ok();
|
||||
}
|
||||
}
|
||||
String::from_utf8_lossy(&buf).into_owned()
|
||||
}
|
||||
|
|
@ -149,14 +155,45 @@ impl Emulator {
|
|||
Ok(())
|
||||
};
|
||||
|
||||
match timeout(Duration::from_secs(10), read).await {
|
||||
Err(e) => error!("read_until_dsr timed out: {buf:?}, error: {e:?}"),
|
||||
Ok(Err(e)) => error!("read_until_dsr failed: {buf:?}, error: {e:?}"),
|
||||
match timeout(Duration::from_secs(5), read).await {
|
||||
Ok(Ok(())) => debug!("read_until_dsr: {buf:?}"),
|
||||
Err(e) => {
|
||||
error!("read_until_dsr timed out: {buf:?}, error: {e:?}");
|
||||
Self::error_to_user().ok();
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("read_until_dsr failed: {buf:?}, error: {e:?}");
|
||||
Self::error_to_user().ok();
|
||||
}
|
||||
}
|
||||
String::from_utf8_lossy(&buf).into_owned()
|
||||
}
|
||||
|
||||
fn error_to_user() -> Result<()> {
|
||||
use crossterm::style::{Attribute, Color, Print, ResetColor, SetAttributes, SetForegroundColor};
|
||||
crossterm::execute!(
|
||||
std::io::stderr(),
|
||||
SetForegroundColor(Color::Red),
|
||||
SetAttributes(Attribute::Bold.into()),
|
||||
Print("\r\nTerminal response timeout: "),
|
||||
ResetColor,
|
||||
SetAttributes(Attribute::Reset.into()),
|
||||
//
|
||||
Print("The request sent by Yazi didn't receive a correct response.\r\n"),
|
||||
Print(
|
||||
"Please check your terminal environment as per: https://yazi-rs.github.io/docs/faq#trt\r\n"
|
||||
),
|
||||
//
|
||||
SetAttributes(Attribute::Bold.into()),
|
||||
SetAttributes(Attribute::Reverse.into()),
|
||||
Print("Press any key to continue...\r\n"),
|
||||
SetAttributes(Attribute::Reset.into()),
|
||||
)?;
|
||||
|
||||
std::io::stdin().read_exact(&mut [0])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn detect_base() -> Result<Self> {
|
||||
defer! { disable_raw_mode().ok(); }
|
||||
enable_raw_mode()?;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub static ADAPTOR: RoCell<Adapter> = RoCell::new();
|
|||
static SHOWN: SyncCell<Option<ratatui::layout::Rect>> = SyncCell::new(None);
|
||||
|
||||
// Tmux support
|
||||
pub static TMUX: RoCell<u8> = RoCell::new();
|
||||
pub static TMUX: RoCell<bool> = RoCell::new();
|
||||
static ESCAPE: RoCell<&'static str> = RoCell::new();
|
||||
static START: RoCell<&'static str> = RoCell::new();
|
||||
static CLOSE: RoCell<&'static str> = RoCell::new();
|
||||
|
|
@ -39,9 +39,9 @@ pub fn init() -> anyhow::Result<()> {
|
|||
pub fn init_default() {
|
||||
// Tmux support
|
||||
TMUX.init(Mux::tmux_passthrough());
|
||||
ESCAPE.init(if *TMUX == 2 { "\x1b\x1b" } else { "\x1b" });
|
||||
START.init(if *TMUX == 2 { "\x1bPtmux;\x1b\x1b" } else { "\x1b" });
|
||||
CLOSE.init(if *TMUX == 2 { "\x1b\\" } else { "" });
|
||||
ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" });
|
||||
START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" });
|
||||
CLOSE.init(if *TMUX { "\x1b\\" } else { "" });
|
||||
|
||||
// WSL support
|
||||
WSL.init(in_wsl());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use tracing::error;
|
||||
use yazi_shared::env_exists;
|
||||
|
||||
use crate::{CLOSE, ESCAPE, Emulator, NVIM, START, TMUX};
|
||||
|
||||
|
|
@ -8,7 +7,7 @@ pub struct Mux;
|
|||
|
||||
impl Mux {
|
||||
pub fn csi(s: &str) -> std::borrow::Cow<str> {
|
||||
if *TMUX == 2 && !*NVIM {
|
||||
if *TMUX && !*NVIM {
|
||||
std::borrow::Cow::Owned(format!(
|
||||
"{}{}{}",
|
||||
*START,
|
||||
|
|
@ -20,9 +19,11 @@ impl Mux {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tmux_passthrough() -> u8 {
|
||||
if !env_exists("TMUX_PANE") || !env_exists("TMUX") {
|
||||
return 0;
|
||||
pub fn tmux_passthrough() -> bool {
|
||||
if !std::env::var("TERM_PROGRAM").is_ok_and(|s| s == "tmux")
|
||||
&& !std::env::var("TERM").is_ok_and(|s| s.starts_with("tmux"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let child = std::process::Command::new("tmux")
|
||||
|
|
@ -33,23 +34,23 @@ impl Mux {
|
|||
.spawn();
|
||||
|
||||
match child.and_then(|c| c.wait_with_output()) {
|
||||
Ok(output) if output.status.success() => return 2,
|
||||
Ok(output) => {
|
||||
Ok(o) if o.status.success() => {}
|
||||
Ok(o) => {
|
||||
error!(
|
||||
"Running `tmux set -p allow-passthrough on` failed: {:?}, {}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
o.status,
|
||||
String::from_utf8_lossy(&o.stderr)
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to spawn `tmux set -p allow-passthrough on`: {err}");
|
||||
Err(e) => {
|
||||
error!("Failed to spawn `tmux set -p allow-passthrough on`: {e}");
|
||||
}
|
||||
}
|
||||
1
|
||||
true
|
||||
}
|
||||
|
||||
pub fn tmux_drain() -> Result<()> {
|
||||
if *TMUX == 2 && !*NVIM {
|
||||
if *TMUX && !*NVIM {
|
||||
crossterm::execute!(std::io::stderr(), crossterm::style::Print(Mux::csi("\x1b[5n")))?;
|
||||
_ = futures::executor::block_on(Emulator::read_until_dsr());
|
||||
}
|
||||
|
|
@ -73,7 +74,7 @@ impl Mux {
|
|||
|
||||
pub(super) fn term_program() -> (Option<String>, Option<String>) {
|
||||
let (mut term, mut program) = (None, None);
|
||||
if *TMUX == 0 {
|
||||
if !*TMUX {
|
||||
return (term, program);
|
||||
}
|
||||
let Ok(output) = std::process::Command::new("tmux").arg("show-environment").output() else {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ impl Actions {
|
|||
|
||||
let emulator = yazi_adapter::Emulator::detect();
|
||||
writeln!(s, "\nEmulator")?;
|
||||
writeln!(s, " TERM : {:?}", env::var_os("TERM"))?;
|
||||
writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?;
|
||||
writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?;
|
||||
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())?;
|
||||
|
|
@ -41,8 +44,8 @@ impl Actions {
|
|||
writeln!(s, "\nWSL")?;
|
||||
writeln!(s, " WSL: {:?}", *yazi_adapter::WSL)?;
|
||||
|
||||
writeln!(s, "\nNVIM")?;
|
||||
writeln!(s, " NVIM : {:?}", *yazi_adapter::NVIM)?;
|
||||
writeln!(s, "\nNeovim")?;
|
||||
writeln!(s, " NVIM : {}", *yazi_adapter::NVIM)?;
|
||||
writeln!(s, " Neovim version: {}", Self::process_output("nvim", "--version"))?;
|
||||
|
||||
writeln!(s, "\nVariables")?;
|
||||
|
|
@ -93,7 +96,7 @@ impl Actions {
|
|||
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
|
||||
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
|
||||
#[rustfmt::skip]
|
||||
writeln!(s, " 7z/7zz : {} / {}", Self::process_output("7z", "i"), Self::process_output("7zz", "i"))?;
|
||||
writeln!(s, " 7zz/7z : {} / {}", Self::process_output("7zz", "i"), Self::process_output("7z", "i"))?;
|
||||
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;
|
||||
|
||||
writeln!(s, "\nClipboard")?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::fs::File;
|
||||
|
||||
use anyhow::Context;
|
||||
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use yazi_fs::Xdg;
|
||||
|
|
@ -34,8 +35,11 @@ impl Logs {
|
|||
.init();
|
||||
|
||||
_GUARD.init(guard);
|
||||
eprintln!("Running with log level `{level}`, logs are written to {log_path:?}");
|
||||
|
||||
Ok(())
|
||||
Ok(crossterm::execute!(
|
||||
std::io::stderr(),
|
||||
SetForegroundColor(Color::Yellow),
|
||||
Print(format!("Running with log level `{level}`, logs are written to {log_path:?}\n")),
|
||||
ResetColor
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{io::{self, BufWriter, Stderr, stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
|
||||
use crossterm::{event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
|
||||
use cursor::RestoreCursor;
|
||||
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
|
||||
use yazi_adapter::{Emulator, Mux};
|
||||
|
|
@ -26,17 +26,18 @@ impl Term {
|
|||
};
|
||||
|
||||
enable_raw_mode()?;
|
||||
if *yazi_adapter::TMUX != 0 {
|
||||
if *yazi_adapter::TMUX {
|
||||
yazi_adapter::Mux::tmux_passthrough();
|
||||
}
|
||||
|
||||
execute!(
|
||||
BufWriter::new(stderr()),
|
||||
screen::SetScreen(true),
|
||||
Print(Mux::csi("\x1bP$q q\x1b\\")), // Request cursor shape (DECRQM)
|
||||
Print(Mux::csi("\x1b[?12$p")), // Request cursor blink status (DECSET)
|
||||
Print("\x1b[?u"), // Request keyboard enhancement flags (CSI u)
|
||||
Print(Mux::csi("\x1b[0c")), // Request device attributes
|
||||
EnterAlternateScreen,
|
||||
screen::SetScreen(false),
|
||||
EnableBracketedPaste,
|
||||
mouse::SetMouse(true),
|
||||
)?;
|
||||
|
|
@ -258,3 +259,24 @@ mod cursor {
|
|||
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
mod screen {
|
||||
use crossterm::terminal::EnterAlternateScreen;
|
||||
use yazi_adapter::TMUX;
|
||||
|
||||
pub struct SetScreen(pub bool);
|
||||
|
||||
impl crossterm::Command for SetScreen {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.0 == *TMUX { Ok(()) } else { EnterAlternateScreen.write_ansi(f) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> {
|
||||
if self.0 == *TMUX { Ok(()) } else { EnterAlternateScreen.execute_winapi() }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_ansi_code_supported(&self) -> bool { EnterAlternateScreen.is_ansi_code_supported() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ function M:peek(job)
|
|||
return require("empty").msg(
|
||||
job,
|
||||
code == 2 and "File list in this archive is encrypted"
|
||||
or "Failed to start both `7z` and `7zz`. Do you have 7-zip installed?"
|
||||
or "Failed to start both `7zz` and `7z`. Do you have 7-zip installed?"
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -55,15 +55,9 @@ function M.spawn_7z(args)
|
|||
return child
|
||||
end
|
||||
|
||||
local child
|
||||
if ya.target_os() == "macos" then
|
||||
child = try("7zz") or try("7z")
|
||||
else
|
||||
child = try("7z") or try("7zz")
|
||||
end
|
||||
|
||||
local child = try("7zz") or try("7z")
|
||||
if not child then
|
||||
return ya.err("Failed to start both `7z` and `7zz`, error: " .. last_err)
|
||||
return ya.err("Failed to start both `7zz` and `7z`, error: " .. last_err)
|
||||
end
|
||||
return child, last_err
|
||||
end
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ function M:try_with(from, pwd, to)
|
|||
local archive = require("archive")
|
||||
local child, err = archive.spawn_7z { "x", "-aou", "-sccUTF-8", "-p" .. pwd, "-o" .. tostring(tmp), tostring(from) }
|
||||
if not child then
|
||||
fail("Failed to start both `7z` and `7zz`, error: " .. err)
|
||||
fail("Failed to start both `7zz` and `7z`, error: " .. err)
|
||||
end
|
||||
|
||||
local output, err = child:wait_with_output()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function M:preload(job)
|
|||
"-font",
|
||||
tostring(job.file.url),
|
||||
"-pointsize",
|
||||
"64",
|
||||
64,
|
||||
"xc:white",
|
||||
"-fill",
|
||||
"black",
|
||||
|
|
|
|||
|
|
@ -19,18 +19,21 @@ function M:preload(job)
|
|||
return 1
|
||||
end
|
||||
|
||||
local status, err = Command("magick"):args({
|
||||
"-density",
|
||||
"200",
|
||||
tostring(job.file.url),
|
||||
"-flatten",
|
||||
"-resize",
|
||||
string.format("%dx%d^", PREVIEW.max_width, PREVIEW.max_height),
|
||||
"-quality",
|
||||
PREVIEW.image_quality,
|
||||
"-auto-orient",
|
||||
"JPG:" .. tostring(cache),
|
||||
}):status()
|
||||
local status, err = Command("magick")
|
||||
:args({
|
||||
"-density",
|
||||
200,
|
||||
tostring(job.file.url),
|
||||
"-flatten",
|
||||
"-resize",
|
||||
string.format("%dx%d^", PREVIEW.max_width, PREVIEW.max_height),
|
||||
"-quality",
|
||||
PREVIEW.image_quality,
|
||||
"-auto-orient",
|
||||
"JPG:" .. tostring(cache),
|
||||
})
|
||||
:env("MAGICK_THREAD_LIMIT", 1)
|
||||
:status()
|
||||
|
||||
if status then
|
||||
return status.success and 1 or 2
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ local function entry()
|
|||
:args({ "query", "-i", "--exclude" })
|
||||
:arg(st.cwd)
|
||||
:env("SHELL", "sh")
|
||||
:env("CLICOLOR", "1")
|
||||
:env("CLICOLOR_FORCE", "1")
|
||||
:env("CLICOLOR", 1)
|
||||
:env("CLICOLOR_FORCE", 1)
|
||||
:env("_ZO_FZF_OPTS", options())
|
||||
:stdin(Command.INHERIT)
|
||||
:stdout(Command.PIPED)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue