diff --git a/Cargo.lock b/Cargo.lock index 8d8ac2ac..954e8a6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,9 +295,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" [[package]] name = "byteorder-lite" @@ -328,9 +328,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.19" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "jobserver", "libc", @@ -355,9 +355,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -787,7 +787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -996,9 +996,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1948,7 +1948,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -2046,7 +2046,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] @@ -2132,7 +2132,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2145,7 +2145,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3000,7 +3000,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3314,6 +3314,7 @@ dependencies = [ "anyhow", "bitflags 2.9.0", "crossterm 0.29.0", + "dirs", "globset", "indexmap", "ratatui", @@ -3431,6 +3432,7 @@ dependencies = [ "yazi-plugin", "yazi-proxy", "yazi-shared", + "yazi-term", "yazi-widgets", ] @@ -3565,6 +3567,14 @@ dependencies = [ "yazi-macro", ] +[[package]] +name = "yazi-term" +version = "25.4.8" +dependencies = [ + "crossterm 0.29.0", + "yazi-macro", +] + [[package]] name = "yazi-widgets" version = "25.4.8" @@ -3582,18 +3592,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index 1063484c..b201d0a6 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -18,6 +18,7 @@ yazi-shared = { path = "../yazi-shared", version = "25.4.8" } anyhow = { workspace = true } bitflags = { workspace = true } crossterm = { workspace = true } +dirs = { workspace = true } globset = { workspace = true } indexmap = { workspace = true } ratatui = { workspace = true } diff --git a/yazi-config/src/mgr/mgr.rs b/yazi-config/src/mgr/mgr.rs index d080834f..c8a65f98 100644 --- a/yazi-config/src/mgr/mgr.rs +++ b/yazi-config/src/mgr/mgr.rs @@ -1,7 +1,7 @@ use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; use yazi_codegen::DeserializeOver2; -use yazi_fs::SortBy; +use yazi_fs::{CWD, SortBy}; use super::{MgrRatio, MouseEvents}; @@ -26,6 +26,21 @@ pub struct Mgr { } impl Mgr { + pub fn title(&self) -> Option { + if self.title_format.is_empty() { + return None; + } + + let home = dirs::home_dir().unwrap_or_default(); + let cwd = if let Ok(p) = CWD.load().strip_prefix(home) { + format!("~{}{}", std::path::MAIN_SEPARATOR, p.display()) + } else { + format!("{}", CWD.load().display()) + }; + + Some(self.title_format.replace("{cwd}", &cwd)) + } + pub(crate) fn reshape(self) -> Result { if self.linemode.is_empty() || self.linemode.len() > 20 { bail!("[mgr].linemode must be between 1 and 20 characters."); diff --git a/yazi-core/src/mgr/commands/refresh.rs b/yazi-core/src/mgr/commands/refresh.rs index 12826927..e2fe4341 100644 --- a/yazi-core/src/mgr/commands/refresh.rs +++ b/yazi-core/src/mgr/commands/refresh.rs @@ -1,5 +1,3 @@ -use std::path::MAIN_SEPARATOR; - use crossterm::{execute, terminal::SetTitle}; use yazi_config::YAZI; use yazi_fs::CWD; @@ -9,8 +7,8 @@ use crate::{mgr::Mgr, tasks::Tasks}; impl Mgr { pub fn refresh(&mut self, _: CmdCow, tasks: &Tasks) { - if CWD.set(self.cwd()) && !YAZI.mgr.title_format.is_empty() { - execute!(TTY.writer(), SetTitle(self.title())).ok(); + if let (_, Some(s)) = (CWD.set(self.cwd()), YAZI.mgr.title()) { + execute!(TTY.writer(), SetTitle(s)).ok(); } self.active_mut().apply_files_attrs(); @@ -27,15 +25,4 @@ impl Mgr { tasks.prework_sorted(&self.current().files); } - - fn title(&self) -> String { - let home = dirs::home_dir().unwrap_or_default(); - let cwd = if let Ok(p) = self.cwd().strip_prefix(home) { - format!("~{}{}", MAIN_SEPARATOR, p.display()) - } else { - format!("{}", self.cwd().display()) - }; - - YAZI.mgr.title_format.replace("{cwd}", &cwd) - } } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index ba62bba7..fa93476f 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -25,6 +25,7 @@ yazi-macro = { path = "../yazi-macro", version = "25.4.8" } yazi-plugin = { path = "../yazi-plugin", version = "25.4.8" } yazi-proxy = { path = "../yazi-proxy", version = "25.4.8" } yazi-shared = { path = "../yazi-shared", version = "25.4.8" } +yazi-term = { path = "../yazi-term", version = "25.4.8" } yazi-widgets = { path = "../yazi-widgets", version = "25.4.8" } # External dependencies diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs index 59bdeea0..1f57c4a6 100644 --- a/yazi-fm/src/term.rs +++ b/yazi-fm/src/term.rs @@ -1,16 +1,13 @@ -use std::{io, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}}; +use std::{io, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}}; use anyhow::Result; -use crossterm::{Command, event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, style::Print, terminal::{LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}}; -use cursor::RestoreCursor; +use crossterm::{Command, event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}}; use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect}; -use yazi_adapter::{Emulator, Mux}; +use yazi_adapter::{Emulator, Mux, TMUX}; use yazi_config::YAZI; 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>>, @@ -34,30 +31,21 @@ impl Term { execute!( TTY.writer(), - screen::SetScreen(true), + yazi_term::If(TMUX.get(), EnterAlternateScreen), Print("\x1bP$q q\x1b\\"), // Request cursor shape (DECRQSS query for DECSCUSR) 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 - screen::SetScreen(false), + yazi_term::If(!TMUX.get(), EnterAlternateScreen), EnableBracketedPaste, - mouse::SetMouse(true), + yazi_term::If(!YAZI.mgr.mouse_events.is_empty(), EnableMouseCapture), )?; let resp = Emulator::read_until_da1(); Mux::tmux_drain()?; + yazi_term::RestoreCursor::store(&resp); CSI_U.store(resp.contains("\x1b[?0u"), Ordering::Relaxed); - BLINK.store(resp.contains("\x1b[?12;1$y"), Ordering::Relaxed); - SHAPE.store( - resp - .split_once("\x1bP1$r") - .and_then(|(_, s)| s.bytes().next()) - .filter(|&b| matches!(b, b'0'..=b'6')) - .map_or(u8::MAX, |b| b - b'0'), - Ordering::Relaxed, - ); - if CSI_U.load(Ordering::Relaxed) { PushKeyboardEnhancementFlags( KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES @@ -66,6 +54,10 @@ impl Term { .write_ansi(&mut TTY.writer())?; } + if let Some(s) = YAZI.mgr.title() { + queue!(TTY.writer(), SetTitle(s)).ok(); + } + term.hide_cursor()?; term.clear()?; term.flush()?; @@ -77,10 +69,14 @@ impl Term { PopKeyboardEnhancementFlags.write_ansi(&mut TTY.writer())?; } + if !YAZI.mgr.title_format.is_empty() { + queue!(TTY.writer(), SetTitle("")).ok(); + } + execute!( TTY.writer(), - mouse::SetMouse(false), - RestoreCursor, + yazi_term::If(!YAZI.mgr.mouse_events.is_empty(), DisableMouseCapture), + yazi_term::RestoreCursor, DisableBracketedPaste, LeaveAlternateScreen, )?; @@ -95,14 +91,13 @@ impl Term { } if !YAZI.mgr.title_format.is_empty() { - execute!(TTY.writer(), SetTitle("")).ok(); + queue!(TTY.writer(), SetTitle("")).ok(); } execute!( TTY.writer(), - mouse::SetMouse(false), - RestoreCursor, - SetTitle(""), + yazi_term::If(!YAZI.mgr.mouse_events.is_empty(), DisableMouseCapture), + yazi_term::RestoreCursor, DisableBracketedPaste, LeaveAlternateScreen, crossterm::cursor::Show @@ -156,98 +151,3 @@ impl Deref for Term { impl DerefMut for Term { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } - -// --- Mouse support -mod mouse { - use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; - use yazi_config::YAZI; - - pub struct SetMouse(pub bool); - - impl crossterm::Command for SetMouse { - fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { - if YAZI.mgr.mouse_events.is_empty() { - Ok(()) - } else if self.0 { - EnableMouseCapture.write_ansi(f) - } else { - DisableMouseCapture.write_ansi(f) - } - } - - #[cfg(windows)] - fn execute_winapi(&self) -> std::io::Result<()> { - if YAZI.mgr.mouse_events.is_empty() { - Ok(()) - } else if self.0 { - EnableMouseCapture.execute_winapi() - } else { - DisableMouseCapture.execute_winapi() - } - } - - #[cfg(windows)] - fn is_ansi_code_supported(&self) -> bool { - if self.0 { - EnableMouseCapture.is_ansi_code_supported() - } else { - DisableMouseCapture.is_ansi_code_supported() - } - } - } -} - -// --- Cursor shape -mod cursor { - use std::sync::atomic::Ordering; - - use crossterm::cursor::SetCursorStyle; - - use super::{BLINK, SHAPE}; - - pub struct RestoreCursor; - - impl crossterm::Command for RestoreCursor { - fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { - let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) { - u8::MAX => (0, false), - n => (n.max(1).div_ceil(2), n.max(1) & 1 == 1), - }; - - let blink = BLINK.load(Ordering::Relaxed) ^ shape_blink; - Ok(match shape { - 2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?, - 2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?, - 3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?, - 3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?, - _ if blink => SetCursorStyle::DefaultUserShape.write_ansi(f)?, - _ if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?, - _ => unreachable!(), - }) - } - - #[cfg(windows)] - 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.get() { Ok(()) } else { EnterAlternateScreen.write_ansi(f) } - } - - #[cfg(windows)] - fn execute_winapi(&self) -> std::io::Result<()> { - if self.0 == TMUX.get() { Ok(()) } else { EnterAlternateScreen.execute_winapi() } - } - - #[cfg(windows)] - fn is_ansi_code_supported(&self) -> bool { EnterAlternateScreen.is_ansi_code_supported() } - } -} diff --git a/yazi-term/Cargo.toml b/yazi-term/Cargo.toml new file mode 100644 index 00000000..b4bd5bad --- /dev/null +++ b/yazi-term/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "yazi-term" +version = "25.4.8" +edition = "2024" +license = "MIT" +authors = [ "sxyazi " ] +description = "Yazi terminal extensions" +homepage = "https://yazi-rs.github.io" +repository = "https://github.com/sxyazi/yazi" +rust-version = "1.85.0" + +[dependencies] +yazi-macro = { path = "../yazi-macro", version = "25.4.8" } + +# External dependencies +crossterm = { workspace = true } diff --git a/yazi-term/src/cursor.rs b/yazi-term/src/cursor.rs new file mode 100644 index 00000000..bd8b1034 --- /dev/null +++ b/yazi-term/src/cursor.rs @@ -0,0 +1,45 @@ +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use crossterm::cursor::SetCursorStyle; + +static BLINK: AtomicBool = AtomicBool::new(false); +static SHAPE: AtomicU8 = AtomicU8::new(0); + +pub struct RestoreCursor; + +impl RestoreCursor { + pub fn store(resp: &str) { + BLINK.store(resp.contains("\x1b[?12;1$y"), Ordering::Relaxed); + SHAPE.store( + resp + .split_once("\x1bP1$r") + .and_then(|(_, s)| s.bytes().next()) + .filter(|&b| matches!(b, b'0'..=b'6')) + .map_or(u8::MAX, |b| b - b'0'), + Ordering::Relaxed, + ); + } +} + +impl crossterm::Command for RestoreCursor { + fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { + let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) { + u8::MAX => (0, false), + n => (n.max(1).div_ceil(2), n.max(1) & 1 == 1), + }; + + let blink = BLINK.load(Ordering::Relaxed) ^ shape_blink; + Ok(match shape { + 2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?, + 2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?, + 3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?, + 3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?, + _ if blink => SetCursorStyle::DefaultUserShape.write_ansi(f)?, + _ if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?, + _ => unreachable!(), + }) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) } +} diff --git a/yazi-term/src/if_.rs b/yazi-term/src/if_.rs new file mode 100644 index 00000000..6aedf1c9 --- /dev/null +++ b/yazi-term/src/if_.rs @@ -0,0 +1,15 @@ +pub struct If(pub bool, pub T); + +impl crossterm::Command for If { + fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { + if self.0 { self.1.write_ansi(f) } else { Ok(()) } + } + + #[cfg(windows)] + fn execute_winapi(&self) -> std::io::Result<()> { + if self.0 { self.1.execute_winapi() } else { Ok(()) } + } + + #[cfg(windows)] + fn is_ansi_code_supported(&self) -> bool { self.1.is_ansi_code_supported() } +} diff --git a/yazi-term/src/lib.rs b/yazi-term/src/lib.rs new file mode 100644 index 00000000..72748161 --- /dev/null +++ b/yazi-term/src/lib.rs @@ -0,0 +1,3 @@ +#![allow(clippy::unit_arg)] + +yazi_macro::mod_flat!(cursor if_);