mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-24 00:01:03 +00:00
feat: support using Yazi in Helix directly without Zellij or tmux (#2461)
This commit is contained in:
parent
31177fd1fd
commit
119cc9c2a1
25 changed files with 354 additions and 284 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -2638,9 +2638,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.0"
|
||||
version = "1.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
|
@ -3329,12 +3329,10 @@ dependencies = [
|
|||
"color_quant",
|
||||
"crossterm",
|
||||
"image",
|
||||
"libc",
|
||||
"ratatui",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"yazi-config",
|
||||
"yazi-macro",
|
||||
"yazi-shared",
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ regex = "1.11.1"
|
|||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.218", features = [ "derive" ] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = { version = "1.43.0", features = [ "full" ] }
|
||||
tokio = { version = "1.44.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.13"
|
||||
toml = { version = "0.8.20" }
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","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","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","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","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit"],"flagWords":[],"version":"0.2","language":"en"}
|
||||
{"version":"0.2","language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","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","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","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","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin"]}
|
||||
|
|
@ -25,11 +25,5 @@ scopeguard = { workspace = true }
|
|||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Threading" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub enum Brand {
|
|||
Foot,
|
||||
Ghostty,
|
||||
Microsoft,
|
||||
Warp,
|
||||
Rio,
|
||||
BlackBox,
|
||||
VSCode,
|
||||
|
|
@ -69,6 +70,7 @@ impl Brand {
|
|||
"iTerm.app" => return Some(B::Iterm2),
|
||||
"WezTerm" => return Some(B::WezTerm),
|
||||
"ghostty" => return Some(B::Ghostty),
|
||||
"WarpTerminal" => return Some(B::Warp),
|
||||
"rio" => return Some(B::Rio),
|
||||
"BlackBox" => return Some(B::BlackBox),
|
||||
"vscode" => return Some(B::VSCode),
|
||||
|
|
@ -99,6 +101,7 @@ impl Brand {
|
|||
B::Foot => &[A::Sixel],
|
||||
B::Ghostty => &[A::Kgp],
|
||||
B::Microsoft => &[A::Sixel],
|
||||
B::Warp => &[],
|
||||
B::Rio => &[A::Iip, A::Sixel],
|
||||
B::BlackBox => &[A::Sixel],
|
||||
B::VSCode => &[A::Iip, A::Sixel],
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use core::str;
|
||||
use std::{io::{LineWriter, Write, stderr}, path::Path};
|
||||
use std::{io::Write, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use image::DynamicImage;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_shared::tty::TTY;
|
||||
|
||||
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
|
||||
|
||||
|
|
@ -26,9 +27,9 @@ impl KgpOld {
|
|||
|
||||
#[inline]
|
||||
pub(crate) fn image_erase(_: Rect) -> Result<()> {
|
||||
let mut stderr = LineWriter::new(stderr());
|
||||
write!(stderr, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
|
||||
stderr.flush()?;
|
||||
let mut w = TTY.lockout();
|
||||
write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?;
|
||||
w.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use std::{io::{LineWriter, stderr}, time::Duration};
|
||||
use std::{io::BufWriter, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use scopeguard::defer;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, warn};
|
||||
use yazi_shared::Either;
|
||||
use yazi_shared::{Either, tty::{Handle, TTY}};
|
||||
|
||||
use crate::{Adapter, AsyncStdin, Brand, Mux, TMUX, Unknown};
|
||||
use crate::{Adapter, Brand, Mux, TMUX, Unknown};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Emulator {
|
||||
|
|
@ -33,7 +33,7 @@ impl Emulator {
|
|||
};
|
||||
|
||||
execute!(
|
||||
LineWriter::new(stderr()),
|
||||
TTY.writer(),
|
||||
SavePosition,
|
||||
Print(kgp_seq), // Detect KGP
|
||||
Print(Mux::csi("\x1b[>q")), // Request terminal version
|
||||
|
|
@ -75,33 +75,33 @@ impl Emulator {
|
|||
|
||||
pub fn move_lock<F, T>((x, y): (u16, u16), cb: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&mut std::io::BufWriter<std::io::StderrLock>) -> Result<T>,
|
||||
F: FnOnce(&mut BufWriter<Handle>) -> Result<T>,
|
||||
{
|
||||
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());
|
||||
let mut w = TTY.lockout();
|
||||
|
||||
// I really don't want to add this,
|
||||
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
|
||||
if TMUX.get() || cfg!(windows) {
|
||||
execute!(buf, SavePosition, MoveTo(x, y), Show)?;
|
||||
execute!(buf, MoveTo(x, y), Show)?;
|
||||
execute!(buf, MoveTo(x, y), Show)?;
|
||||
execute!(w, SavePosition, MoveTo(x, y), Show)?;
|
||||
execute!(w, MoveTo(x, y), Show)?;
|
||||
execute!(w, MoveTo(x, y), Show)?;
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
} else {
|
||||
queue!(buf, SavePosition, MoveTo(x, y))?;
|
||||
queue!(w, SavePosition, MoveTo(x, y))?;
|
||||
}
|
||||
|
||||
let result = cb(&mut buf);
|
||||
let result = cb(&mut w);
|
||||
if TMUX.get() || cfg!(windows) {
|
||||
queue!(buf, Hide, RestorePosition)?;
|
||||
queue!(w, Hide, RestorePosition)?;
|
||||
} else {
|
||||
queue!(buf, RestorePosition)?;
|
||||
queue!(w, RestorePosition)?;
|
||||
}
|
||||
|
||||
buf.flush()?;
|
||||
w.flush()?;
|
||||
result
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ impl Emulator {
|
|||
let now = std::time::Instant::now();
|
||||
let h = tokio::spawn(Self::error_to_user());
|
||||
|
||||
let (buf, result) = AsyncStdin::default().read_until(Duration::from_millis(500), |b, buf| {
|
||||
let (buf, result) = TTY.read_until(Duration::from_millis(500), |b, buf| {
|
||||
b == b'c'
|
||||
&& buf.contains(&0x1b)
|
||||
&& buf.rsplitn(2, |&b| b == 0x1b).next().is_some_and(|s| s.starts_with(b"[?"))
|
||||
|
|
@ -128,7 +128,7 @@ impl Emulator {
|
|||
|
||||
pub fn read_until_dsr() -> String {
|
||||
let now = std::time::Instant::now();
|
||||
let (buf, result) = AsyncStdin::default().read_until(Duration::from_millis(100), |b, buf| {
|
||||
let (buf, result) = TTY.read_until(Duration::from_millis(100), |b, buf| {
|
||||
b == b'n' && (buf.ends_with(b"\x1b[0n") || buf.ends_with(b"\x1b[3n"))
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
yazi_macro::mod_pub!(drivers);
|
||||
|
||||
yazi_macro::mod_flat!(adapter brand dimension emulator image info mux stdin unknown);
|
||||
yazi_macro::mod_flat!(adapter brand dimension emulator image info mux unknown);
|
||||
|
||||
use yazi_shared::{SyncCell, in_wsl};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use tracing::error;
|
||||
use yazi_macro::time;
|
||||
use yazi_shared::tty::TTY;
|
||||
|
||||
use crate::{CLOSE, ESCAPE, Emulator, START, TMUX};
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ impl Mux {
|
|||
|
||||
pub fn tmux_drain() -> Result<()> {
|
||||
if TMUX.get() {
|
||||
crossterm::execute!(std::io::stderr(), crossterm::style::Print(Mux::csi("\x1b[5n")))?;
|
||||
crossterm::execute!(TTY.writer(), crossterm::style::Print(Mux::csi("\x1b[5n")))?;
|
||||
_ = Emulator::read_until_dsr();
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
use std::{io::{Error, ErrorKind}, ops::Deref, time::{Duration, Instant}};
|
||||
|
||||
pub struct AsyncStdin {
|
||||
fd: Fd,
|
||||
}
|
||||
|
||||
impl Default for AsyncStdin {
|
||||
fn default() -> Self { Self { fd: Fd::new().expect("failed to open stdin") } }
|
||||
}
|
||||
|
||||
impl AsyncStdin {
|
||||
pub fn read_until<P>(&mut self, timeout: Duration, predicate: P) -> (Vec<u8>, std::io::Result<()>)
|
||||
where
|
||||
P: Fn(u8, &[u8]) -> bool,
|
||||
{
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(200);
|
||||
let now = Instant::now();
|
||||
|
||||
let mut read = || {
|
||||
loop {
|
||||
if now.elapsed() > timeout {
|
||||
return Err(Error::new(ErrorKind::TimedOut, "timed out"));
|
||||
} else if !self.poll(Duration::from_millis(30))? {
|
||||
continue;
|
||||
}
|
||||
|
||||
let b = self.read_u8()?;
|
||||
buf.push(b);
|
||||
|
||||
if predicate(b, &buf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let result = read();
|
||||
(buf, result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Unix
|
||||
#[cfg(unix)]
|
||||
impl AsyncStdin {
|
||||
pub fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {
|
||||
let mut tv = libc::timeval {
|
||||
tv_sec: timeout.as_secs() as libc::time_t,
|
||||
tv_usec: timeout.subsec_micros() as libc::suseconds_t,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
let mut set: libc::fd_set = std::mem::zeroed();
|
||||
libc::FD_ZERO(&mut set);
|
||||
libc::FD_SET(*self.fd, &mut set);
|
||||
libc::select(*self.fd + 1, &mut set, std::ptr::null_mut(), std::ptr::null_mut(), &mut tv)
|
||||
};
|
||||
|
||||
match result {
|
||||
-1 => Err(Error::last_os_error()),
|
||||
0 => Ok(false),
|
||||
_ => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
let mut b = 0;
|
||||
match unsafe { libc::read(*self.fd, &mut b as *mut _ as *mut _, 1) } {
|
||||
-1 => Err(Error::last_os_error()),
|
||||
0 => Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")),
|
||||
_ => Ok(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Windows
|
||||
#[cfg(windows)]
|
||||
impl AsyncStdin {
|
||||
pub fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {
|
||||
use windows_sys::Win32::{Foundation::{WAIT_FAILED, WAIT_OBJECT_0}, System::Threading::WaitForSingleObject};
|
||||
|
||||
let millis = timeout.as_millis();
|
||||
match unsafe { WaitForSingleObject(*self.fd, millis as u32) } {
|
||||
WAIT_FAILED => Err(Error::last_os_error()),
|
||||
WAIT_OBJECT_0 => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
use windows_sys::Win32::Storage::FileSystem::ReadFile;
|
||||
|
||||
let mut buf = 0;
|
||||
let mut bytes = 0;
|
||||
let success = unsafe { ReadFile(*self.fd, &mut buf, 1, &mut bytes, std::ptr::null_mut()) };
|
||||
|
||||
if success == 0 {
|
||||
return Err(Error::last_os_error());
|
||||
} else if bytes == 0 {
|
||||
return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF"));
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fd
|
||||
struct Fd {
|
||||
#[cfg(unix)]
|
||||
inner: std::os::fd::RawFd,
|
||||
#[cfg(windows)]
|
||||
inner: std::os::windows::io::RawHandle,
|
||||
close: bool,
|
||||
}
|
||||
|
||||
impl Deref for Fd {
|
||||
#[cfg(unix)]
|
||||
type Target = std::os::fd::RawFd;
|
||||
#[cfg(windows)]
|
||||
type Target = std::os::windows::io::RawHandle;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl Drop for Fd {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(unix)]
|
||||
if self.close {
|
||||
unsafe { libc::close(self.inner) };
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if self.close {
|
||||
unsafe { windows_sys::Win32::Foundation::CloseHandle(self.inner) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fd {
|
||||
#[cfg(unix)]
|
||||
fn new() -> std::io::Result<Self> {
|
||||
use std::{fs::OpenOptions, os::fd::IntoRawFd};
|
||||
|
||||
Ok(if unsafe { libc::isatty(libc::STDIN_FILENO) } == 1 {
|
||||
Self { inner: libc::STDIN_FILENO, close: false }
|
||||
} else {
|
||||
Self {
|
||||
inner: OpenOptions::new().read(true).write(true).open("/dev/tty")?.into_raw_fd(),
|
||||
close: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn new() -> std::io::Result<Self> {
|
||||
use windows_sys::Win32::{Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}};
|
||||
|
||||
let name: Vec<u16> = "CONIN$\0".encode_utf16().collect();
|
||||
let result = unsafe {
|
||||
CreateFileW(
|
||||
name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
std::ptr::null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result == INVALID_HANDLE_VALUE {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(Self { inner: result, close: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,8 @@ impl Actions {
|
|||
writeln!(s, " Emulator.detect : {:?}", yazi_adapter::EMULATOR)?;
|
||||
|
||||
writeln!(s, "\nAdapter")?;
|
||||
writeln!(s, " Adapter.matches: {:?}", yazi_adapter::ADAPTOR)?;
|
||||
writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?;
|
||||
writeln!(s, " Dimension.available: {:?}", yazi_adapter::Dimension::available())?;
|
||||
|
||||
writeln!(s, "\nDesktop")?;
|
||||
writeln!(s, " XDG_SESSION_TYPE : {:?}", env::var_os("XDG_SESSION_TYPE"))?;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ yazi_macro::mod_pub!(keymap mgr open plugin popup preview tasks theme which);
|
|||
|
||||
yazi_macro::mod_flat!(layout pattern preset priority);
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::{io::{Read, Write}, str::FromStr};
|
||||
|
||||
use yazi_shared::{RoCell, SyncCell};
|
||||
use yazi_shared::{RoCell, SyncCell, tty::TTY};
|
||||
|
||||
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
|
||||
pub static MGR: RoCell<mgr::Mgr> = RoCell::new();
|
||||
|
|
@ -93,14 +93,16 @@ fn try_init(merge: bool) -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {
|
||||
eprintln!("{e}");
|
||||
let stdout = &mut *TTY.lockout();
|
||||
|
||||
writeln!(stdout, "{e}")?;
|
||||
if let Some(src) = e.source() {
|
||||
eprintln!("\nCaused by:\n{src}");
|
||||
writeln!(stdout, "\nCaused by:\n{src}")?;
|
||||
}
|
||||
|
||||
use crossterm::style::{Attribute, Print, SetAttributes};
|
||||
crossterm::execute!(
|
||||
std::io::stderr(),
|
||||
stdout,
|
||||
SetAttributes(Attribute::Reverse.into()),
|
||||
SetAttributes(Attribute::Bold.into()),
|
||||
Print("Press <Enter> to continue with preset settings..."),
|
||||
|
|
@ -108,6 +110,6 @@ fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {
|
|||
Print("\n"),
|
||||
)?;
|
||||
|
||||
std::io::stdin().read_line(&mut String::new())?;
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use std::{borrow::Cow, collections::HashMap, ffi::{OsStr, OsString}, io::{BufWriter, Write, stderr}, path::PathBuf};
|
||||
use std::{borrow::Cow, collections::HashMap, ffi::{OsStr, OsString}, io::{Read, Write}, path::PathBuf};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use scopeguard::defer;
|
||||
use tokio::{fs::{self, OpenOptions}, io::{AsyncReadExt, AsyncWriteExt, stdin}};
|
||||
use tokio::{fs::{self, OpenOptions}, io::AsyncWriteExt};
|
||||
use yazi_config::{OPEN, PREVIEW};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, paths_to_same_file};
|
||||
use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER};
|
||||
use yazi_shared::{terminal_clear, url::Url};
|
||||
use yazi_shared::{terminal_clear, tty::TTY, url::Url};
|
||||
|
||||
use crate::mgr::Mgr;
|
||||
|
||||
|
|
@ -51,10 +51,10 @@ impl Mgr {
|
|||
}
|
||||
|
||||
async fn bulk_rename_do(root: PathBuf, old: Vec<PathBuf>, new: Vec<PathBuf>) -> Result<()> {
|
||||
terminal_clear(&mut stderr())?;
|
||||
terminal_clear(TTY.writer())?;
|
||||
if old.len() != new.len() {
|
||||
eprintln!("Number of old and new differ, press ENTER to exit");
|
||||
stdin().read_exact(&mut [0]).await?;
|
||||
TTY.writer().write_all(b"Number of old and new differ, press ENTER to exit")?;
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -65,16 +65,16 @@ impl Mgr {
|
|||
}
|
||||
|
||||
{
|
||||
let mut stderr = BufWriter::new(stderr().lock());
|
||||
let mut w = TTY.lockout();
|
||||
for (o, n) in &todo {
|
||||
writeln!(stderr, "{} -> {}", o.display(), n.display())?;
|
||||
writeln!(w, "{} -> {}", o.display(), n.display())?;
|
||||
}
|
||||
write!(stderr, "Continue to rename? (y/N): ")?;
|
||||
stderr.flush()?;
|
||||
write!(w, "Continue to rename? (y/N): ")?;
|
||||
w.flush()?;
|
||||
}
|
||||
|
||||
let mut buf = [0; 10];
|
||||
_ = stdin().read(&mut buf).await?;
|
||||
_ = TTY.reader().read(&mut buf)?;
|
||||
if buf[0] != b'y' && buf[0] != b'Y' {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -108,19 +108,17 @@ impl Mgr {
|
|||
}
|
||||
|
||||
async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> {
|
||||
terminal_clear(&mut stderr())?;
|
||||
let mut stdout = TTY.lockout();
|
||||
terminal_clear(&mut *stdout)?;
|
||||
|
||||
{
|
||||
let mut stderr = BufWriter::new(stderr().lock());
|
||||
writeln!(stderr, "Failed to rename:")?;
|
||||
for (o, n, e) in failed {
|
||||
writeln!(stderr, "{} -> {}: {e}", o.display(), n.display())?;
|
||||
}
|
||||
writeln!(stderr, "\nPress ENTER to exit")?;
|
||||
stderr.flush()?;
|
||||
writeln!(stdout, "Failed to rename:")?;
|
||||
for (old, new, err) in failed {
|
||||
writeln!(stdout, "{} -> {}: {err}", old.display(), new.display())?;
|
||||
}
|
||||
writeln!(stdout, "\nPress ENTER to exit")?;
|
||||
|
||||
stdin().read_exact(&mut [0]).await?;
|
||||
stdout.flush()?;
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ use std::path::MAIN_SEPARATOR;
|
|||
use crossterm::{execute, terminal::SetTitle};
|
||||
use yazi_config::MGR;
|
||||
use yazi_fs::CWD;
|
||||
use yazi_shared::event::CmdCow;
|
||||
use yazi_shared::{event::CmdCow, tty::TTY};
|
||||
|
||||
use crate::{mgr::Mgr, tasks::Tasks};
|
||||
|
||||
impl Mgr {
|
||||
pub fn refresh(&mut self, _: CmdCow, tasks: &Tasks) {
|
||||
if CWD.set(self.cwd()) && !MGR.title_format.is_empty() {
|
||||
execute!(std::io::stderr(), SetTitle(self.title())).ok();
|
||||
execute!(TTY.writer(), SetTitle(self.title())).ok();
|
||||
}
|
||||
|
||||
self.active_mut().apply_files_attrs();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{io::{BufWriter, LineWriter, Write, stderr}, mem};
|
||||
use std::io::Write;
|
||||
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use scopeguard::defer;
|
||||
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
use yazi_shared::{event::CmdCow, terminal_clear};
|
||||
use yazi_shared::{event::CmdCow, terminal_clear, tty::TTY};
|
||||
|
||||
use crate::tasks::Tasks;
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ impl Tasks {
|
|||
let _permit = HIDER.acquire().await.unwrap();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let mut buffered = {
|
||||
let buffered = {
|
||||
let mut ongoing = ongoing.lock();
|
||||
let Some(task) = ongoing.get_mut(id) else { return };
|
||||
|
||||
|
|
@ -30,24 +30,22 @@ impl Tasks {
|
|||
defer!(AppProxy::resume());
|
||||
AppProxy::stop().await;
|
||||
|
||||
terminal_clear(&mut stderr()).ok();
|
||||
BufWriter::new(stderr().lock()).write_all(mem::take(&mut buffered).as_bytes()).ok();
|
||||
terminal_clear(TTY.writer()).ok();
|
||||
TTY.writer().write_all(buffered.as_bytes()).ok();
|
||||
|
||||
defer! { disable_raw_mode().ok(); }
|
||||
enable_raw_mode().ok();
|
||||
|
||||
let mut stdin = stdin();
|
||||
let mut stdin = stdin(); // TODO: stdin
|
||||
let mut answer = 0;
|
||||
loop {
|
||||
select! {
|
||||
Some(line) = rx.recv() => {
|
||||
let mut stderr = LineWriter::new(stderr().lock());
|
||||
stderr.write_all(line.as_bytes()).ok();
|
||||
stderr.write_all(b"\r\n").ok();
|
||||
execute!(TTY.writer(), crossterm::style::Print(line), crossterm::style::Print("\r\n")).ok();
|
||||
}
|
||||
_ = time::sleep(time::Duration::from_millis(500)) => {
|
||||
if ongoing.lock().get(id).is_none() {
|
||||
stderr().write_all(b"Task finished, press `q` to quit\r\n").ok();
|
||||
execute!(TTY.writer(), crossterm::style::Print("Task finished, press `q` to quit\r\n")).ok();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::{io::{BufWriter, stderr}, sync::atomic::Ordering};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crossterm::{execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
||||
use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer};
|
||||
use scopeguard::defer;
|
||||
use yazi_plugin::elements::COLLISION;
|
||||
use yazi_shared::event::NEED_RENDER;
|
||||
use yazi_shared::{event::NEED_RENDER, tty::TTY};
|
||||
|
||||
use crate::{app::App, lives::Lives, root::Root};
|
||||
|
||||
|
|
@ -13,8 +13,8 @@ impl App {
|
|||
NEED_RENDER.store(false, Ordering::Relaxed);
|
||||
let Some(term) = &mut self.term else { return };
|
||||
|
||||
queue!(stderr(), BeginSynchronizedUpdate).ok();
|
||||
defer! { execute!(stderr(), EndSynchronizedUpdate).ok(); }
|
||||
queue!(TTY.writer(), BeginSynchronizedUpdate).ok();
|
||||
defer! { execute!(TTY.writer(), EndSynchronizedUpdate).ok(); }
|
||||
|
||||
let collision = COLLISION.swap(false, Ordering::Relaxed);
|
||||
let frame = term
|
||||
|
|
@ -79,7 +79,9 @@ impl App {
|
|||
}
|
||||
|
||||
let patches = frame.buffer.diff(&new);
|
||||
let mut backend = CrosstermBackend::new(BufWriter::new(stderr().lock()));
|
||||
let stdout = &mut *TTY.lockout();
|
||||
|
||||
let mut backend = CrosstermBackend::new(stdout);
|
||||
backend.draw(patches.into_iter()).ok();
|
||||
if let Some(pos) = cursor {
|
||||
backend.show_cursor().ok();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{io::{self, BufWriter, Stderr, stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}};
|
||||
use std::{io, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
|
||||
|
|
@ -6,14 +6,14 @@ use cursor::RestoreCursor;
|
|||
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
|
||||
use yazi_adapter::{Emulator, Mux};
|
||||
use yazi_config::{INPUT, MGR};
|
||||
use yazi_shared::SyncCell;
|
||||
use yazi_shared::{SyncCell, tty::{TTY, TtyWriter}};
|
||||
|
||||
static CSI_U: AtomicBool = AtomicBool::new(false);
|
||||
static BLINK: AtomicBool = AtomicBool::new(false);
|
||||
static SHAPE: AtomicU8 = AtomicU8::new(0);
|
||||
|
||||
pub(super) struct Term {
|
||||
inner: Terminal<CrosstermBackend<BufWriter<Stderr>>>,
|
||||
inner: Terminal<CrosstermBackend<TtyWriter<'static>>>,
|
||||
last_area: Rect,
|
||||
last_buffer: Buffer,
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ impl Term {
|
|||
pub(super) fn start() -> Result<Self> {
|
||||
static SKIP: SyncCell<bool> = SyncCell::new(false);
|
||||
let mut term = Self {
|
||||
inner: Terminal::new(CrosstermBackend::new(BufWriter::new(stderr())))?,
|
||||
inner: Terminal::new(CrosstermBackend::new(TTY.writer()))?,
|
||||
last_area: Default::default(),
|
||||
last_buffer: Default::default(),
|
||||
};
|
||||
|
|
@ -33,7 +33,7 @@ impl Term {
|
|||
}
|
||||
|
||||
execute!(
|
||||
BufWriter::new(stderr()),
|
||||
TTY.writer(),
|
||||
screen::SetScreen(true),
|
||||
Print(Mux::csi("\x1bP$q q\x1b\\")), // Request cursor shape (DECRQSS query for DECSCUSR)
|
||||
Print(Mux::csi("\x1b[?12$p")), // Request cursor blink status (DECSET)
|
||||
|
|
@ -60,7 +60,7 @@ impl Term {
|
|||
|
||||
if CSI_U.load(Ordering::Relaxed) {
|
||||
queue!(
|
||||
stderr(),
|
||||
TTY.writer(),
|
||||
PushKeyboardEnhancementFlags(
|
||||
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
|
||||
|
|
@ -76,11 +76,11 @@ impl Term {
|
|||
|
||||
fn stop(&mut self) -> Result<()> {
|
||||
if CSI_U.swap(false, Ordering::Relaxed) {
|
||||
execute!(stderr(), PopKeyboardEnhancementFlags)?;
|
||||
execute!(TTY.writer(), PopKeyboardEnhancementFlags)?;
|
||||
}
|
||||
|
||||
execute!(
|
||||
stderr(),
|
||||
TTY.writer(),
|
||||
mouse::SetMouse(false),
|
||||
RestoreCursor,
|
||||
DisableBracketedPaste,
|
||||
|
|
@ -93,15 +93,15 @@ impl Term {
|
|||
|
||||
pub(super) fn goodbye(f: impl FnOnce() -> bool) -> ! {
|
||||
if CSI_U.swap(false, Ordering::Relaxed) {
|
||||
execute!(stderr(), PopKeyboardEnhancementFlags).ok();
|
||||
execute!(TTY.writer(), PopKeyboardEnhancementFlags).ok();
|
||||
}
|
||||
|
||||
if !MGR.title_format.is_empty() {
|
||||
execute!(stderr(), SetTitle("")).ok();
|
||||
execute!(TTY.writer(), SetTitle("")).ok();
|
||||
}
|
||||
|
||||
execute!(
|
||||
stderr(),
|
||||
TTY.writer(),
|
||||
mouse::SetMouse(false),
|
||||
RestoreCursor,
|
||||
SetTitle(""),
|
||||
|
|
@ -148,9 +148,9 @@ impl Term {
|
|||
pub(super) fn set_cursor_block() -> Result<()> {
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
Ok(if INPUT.cursor_blink {
|
||||
queue!(stderr(), SetCursorStyle::BlinkingBlock)?
|
||||
queue!(TTY.writer(), SetCursorStyle::BlinkingBlock)?
|
||||
} else {
|
||||
queue!(stderr(), SetCursorStyle::SteadyBlock)?
|
||||
queue!(TTY.writer(), SetCursorStyle::SteadyBlock)?
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -158,9 +158,9 @@ impl Term {
|
|||
pub(super) fn set_cursor_bar() -> Result<()> {
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
Ok(if INPUT.cursor_blink {
|
||||
queue!(stderr(), SetCursorStyle::BlinkingBar)?
|
||||
queue!(TTY.writer(), SetCursorStyle::BlinkingBar)?
|
||||
} else {
|
||||
queue!(stderr(), SetCursorStyle::SteadyBar)?
|
||||
queue!(TTY.writer(), SetCursorStyle::SteadyBar)?
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -168,9 +168,9 @@ impl Term {
|
|||
pub(super) fn set_cursor_underscore() -> Result<()> {
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
Ok(if INPUT.cursor_blink {
|
||||
queue!(stderr(), SetCursorStyle::BlinkingUnderScore)?
|
||||
queue!(TTY.writer(), SetCursorStyle::BlinkingUnderScore)?
|
||||
} else {
|
||||
queue!(stderr(), SetCursorStyle::SteadyUnderScore)?
|
||||
queue!(TTY.writer(), SetCursorStyle::SteadyUnderScore)?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ impl Drop for Term {
|
|||
}
|
||||
|
||||
impl Deref for Term {
|
||||
type Target = Terminal<CrosstermBackend<BufWriter<Stderr>>>;
|
||||
type Target = Terminal<CrosstermBackend<TtyWriter<'static>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,13 +55,14 @@ impl Clipboard {
|
|||
|
||||
#[cfg(unix)]
|
||||
pub async fn set(&self, s: impl AsRef<std::ffi::OsStr>) {
|
||||
use std::{io::{BufWriter, stderr}, process::Stdio};
|
||||
use std::process::Stdio;
|
||||
|
||||
use crossterm::execute;
|
||||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use yazi_shared::tty::TTY;
|
||||
|
||||
s.as_ref().clone_into(&mut self.content.lock());
|
||||
execute!(BufWriter::new(stderr()), osc52::SetClipboard::new(s.as_ref())).ok();
|
||||
execute!(TTY.writer(), osc52::SetClipboard::new(s.as_ref())).ok();
|
||||
|
||||
let all = [
|
||||
("pbcopy", &[][..]),
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ libc = { workspace = true }
|
|||
uzers = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.59.0", features = [ "Win32_UI_Shell" ] }
|
||||
windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Threading", "Win32_UI_Shell" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
#![allow(clippy::option_map_unit_fn)]
|
||||
|
||||
yazi_macro::mod_pub!(errors event shell theme translit url);
|
||||
yazi_macro::mod_pub!(errors event shell theme translit tty url);
|
||||
|
||||
yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time);
|
||||
|
||||
pub fn init() {
|
||||
tty::init();
|
||||
|
||||
LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default()));
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ pub fn hostname() -> Option<&'static str> {
|
|||
|
||||
#[cfg(unix)]
|
||||
fn hostname_impl() -> Result<String, std::io::Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
use libc::{gethostname, strlen};
|
||||
|
||||
let mut s = [0; 256];
|
||||
|
|
@ -24,6 +22,6 @@ fn hostname_impl() -> Result<String, std::io::Error> {
|
|||
};
|
||||
|
||||
std::str::from_utf8(&s[..len])
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "invalid hostname"))
|
||||
.map_err(|_| std::io::Error::other("invalid hostname"))
|
||||
.map(|s| s.to_owned())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::io::Write;
|
||||
|
||||
use crossterm::queue;
|
||||
use crossterm::execute;
|
||||
|
||||
#[inline]
|
||||
pub fn terminal_clear(w: &mut impl Write) -> std::io::Result<()> {
|
||||
queue!(w, crossterm::terminal::Clear(crossterm::terminal::ClearType::All))?;
|
||||
writeln!(w)?;
|
||||
w.flush()
|
||||
pub fn terminal_clear(mut w: impl Write) -> std::io::Result<()> {
|
||||
execute!(
|
||||
w,
|
||||
crossterm::terminal::Clear(crossterm::terminal::ClearType::All),
|
||||
crossterm::style::Print("\n")
|
||||
)
|
||||
}
|
||||
|
|
|
|||
164
yazi-shared/src/tty/handle.rs
Normal file
164
yazi-shared/src/tty/handle.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
use std::{io::{Error, ErrorKind, Read, Write}, time::Duration};
|
||||
|
||||
pub struct Handle {
|
||||
#[cfg(unix)]
|
||||
inner: std::os::fd::RawFd,
|
||||
#[cfg(windows)]
|
||||
inner: std::os::windows::io::RawHandle,
|
||||
close: bool,
|
||||
}
|
||||
|
||||
impl Drop for Handle {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(unix)]
|
||||
if self.close {
|
||||
unsafe { libc::close(self.inner) };
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if self.close {
|
||||
unsafe { windows_sys::Win32::Foundation::CloseHandle(self.inner) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Handle {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::{fd::IntoRawFd, unix::io::FromRawFd};
|
||||
let mut f = unsafe { std::fs::File::from_raw_fd(self.inner) };
|
||||
let result = f.read(buf);
|
||||
self.inner = f.into_raw_fd();
|
||||
result
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawHandle, IntoRawHandle};
|
||||
let mut f = unsafe { std::fs::File::from_raw_handle(self.inner) };
|
||||
let result = f.read(buf);
|
||||
self.inner = f.into_raw_handle();
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Handle {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::{fd::IntoRawFd, unix::io::FromRawFd};
|
||||
let mut f = unsafe { std::fs::File::from_raw_fd(self.inner) };
|
||||
let result = f.write(buf);
|
||||
self.inner = f.into_raw_fd();
|
||||
result
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::io::{FromRawHandle, IntoRawHandle};
|
||||
let mut f = unsafe { std::fs::File::from_raw_handle(self.inner) };
|
||||
let result = f.write(buf);
|
||||
self.inner = f.into_raw_handle();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Handle {
|
||||
pub(super) fn new(out: bool) -> std::io::Result<Self> {
|
||||
use std::{fs::OpenOptions, os::fd::IntoRawFd};
|
||||
|
||||
let fileno = if out { libc::STDOUT_FILENO } else { libc::STDIN_FILENO };
|
||||
Ok(if unsafe { libc::isatty(fileno) } == 1 {
|
||||
Self { inner: fileno, close: false }
|
||||
} else {
|
||||
Self {
|
||||
inner: OpenOptions::new().read(!out).write(out).open("/dev/tty")?.into_raw_fd(),
|
||||
close: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {
|
||||
let mut tv = libc::timeval {
|
||||
tv_sec: timeout.as_secs() as libc::time_t,
|
||||
tv_usec: timeout.subsec_micros() as libc::suseconds_t,
|
||||
};
|
||||
|
||||
let result = unsafe {
|
||||
let mut set: libc::fd_set = std::mem::zeroed();
|
||||
libc::FD_ZERO(&mut set);
|
||||
libc::FD_SET(self.inner, &mut set);
|
||||
libc::select(self.inner + 1, &mut set, std::ptr::null_mut(), std::ptr::null_mut(), &mut tv)
|
||||
};
|
||||
|
||||
match result {
|
||||
-1 => Err(Error::last_os_error()),
|
||||
0 => Ok(false),
|
||||
_ => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
let mut b = 0;
|
||||
match unsafe { libc::read(self.inner, &mut b as *mut _ as *mut _, 1) } {
|
||||
-1 => Err(Error::last_os_error()),
|
||||
0 => Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")),
|
||||
_ => Ok(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl Handle {
|
||||
pub(super) fn new(out: bool) -> std::io::Result<Self> {
|
||||
use windows_sys::Win32::{Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}};
|
||||
|
||||
let name: Vec<u16> = if out { "CONOUT$\0" } else { "CONIN$\0" }.encode_utf16().collect();
|
||||
let result = unsafe {
|
||||
CreateFileW(
|
||||
name.as_ptr(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
std::ptr::null_mut(),
|
||||
OPEN_EXISTING,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result == INVALID_HANDLE_VALUE {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(Self { inner: result, close: true })
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn poll(&mut self, timeout: Duration) -> std::io::Result<bool> {
|
||||
use windows_sys::Win32::{Foundation::{WAIT_FAILED, WAIT_OBJECT_0}, System::Threading::WaitForSingleObject};
|
||||
|
||||
let millis = timeout.as_millis();
|
||||
match unsafe { WaitForSingleObject(self.inner, millis as u32) } {
|
||||
WAIT_FAILED => Err(Error::last_os_error()),
|
||||
WAIT_OBJECT_0 => Ok(true),
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn read_u8(&mut self) -> std::io::Result<u8> {
|
||||
use windows_sys::Win32::Storage::FileSystem::ReadFile;
|
||||
|
||||
let mut buf = 0;
|
||||
let mut bytes = 0;
|
||||
let success = unsafe { ReadFile(self.inner, &mut buf, 1, &mut bytes, std::ptr::null_mut()) };
|
||||
|
||||
if success == 0 {
|
||||
return Err(Error::last_os_error());
|
||||
} else if bytes == 0 {
|
||||
return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF"));
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
5
yazi-shared/src/tty/mod.rs
Normal file
5
yazi-shared/src/tty/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
yazi_macro::mod_flat!(tty handle);
|
||||
|
||||
pub static TTY: crate::RoCell<Tty> = crate::RoCell::new();
|
||||
|
||||
pub(super) fn init() { TTY.with(<_>::default); }
|
||||
74
yazi-shared/src/tty/tty.rs
Normal file
74
yazi-shared/src/tty/tty.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use std::{io::{BufWriter, Error, ErrorKind, Read, Write}, time::{Duration, Instant}};
|
||||
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
|
||||
use super::Handle;
|
||||
|
||||
pub struct Tty {
|
||||
stdin: Mutex<Handle>,
|
||||
stdout: Mutex<BufWriter<Handle>>,
|
||||
}
|
||||
|
||||
impl Default for Tty {
|
||||
fn default() -> Self {
|
||||
let stdin = Handle::new(false).expect("failed to open stdin");
|
||||
let stdout = Handle::new(true).expect("failed to open stdout");
|
||||
Self { stdin: Mutex::new(stdin), stdout: Mutex::new(BufWriter::new(stdout)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Tty {
|
||||
pub const fn reader(&self) -> TtyReader { TtyReader(&self.stdin) }
|
||||
|
||||
pub const fn writer(&self) -> TtyWriter { TtyWriter(&self.stdout) }
|
||||
|
||||
pub fn lockin(&self) -> MutexGuard<Handle> { self.stdin.lock() }
|
||||
|
||||
pub fn lockout(&self) -> MutexGuard<BufWriter<Handle>> { self.stdout.lock() }
|
||||
|
||||
pub fn read_until<P>(&self, timeout: Duration, predicate: P) -> (Vec<u8>, std::io::Result<()>)
|
||||
where
|
||||
P: Fn(u8, &[u8]) -> bool,
|
||||
{
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(200);
|
||||
let now = Instant::now();
|
||||
|
||||
let mut read = || {
|
||||
let mut stdin = self.stdin.lock();
|
||||
loop {
|
||||
if now.elapsed() > timeout {
|
||||
return Err(Error::new(ErrorKind::TimedOut, "timed out"));
|
||||
} else if !stdin.poll(Duration::from_millis(30))? {
|
||||
continue;
|
||||
}
|
||||
|
||||
let b = stdin.read_u8()?;
|
||||
buf.push(b);
|
||||
|
||||
if predicate(b, &buf) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let result = read();
|
||||
(buf, result)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Reader
|
||||
pub struct TtyReader<'a>(&'a Mutex<Handle>);
|
||||
|
||||
impl Read for TtyReader<'_> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.0.lock().read(buf) }
|
||||
}
|
||||
|
||||
// --- Writer
|
||||
pub struct TtyWriter<'a>(&'a Mutex<BufWriter<Handle>>);
|
||||
|
||||
impl Write for TtyWriter<'_> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { self.0.lock().write(buf) }
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> { self.0.lock().flush() }
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue