From dc46e90b0a7ef3b9633ce40f962279810ffc4c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Thu, 6 Mar 2025 18:54:13 +0800 Subject: [PATCH 01/11] fix: always show the size in the status bar even in empty directories (#2449) --- README.md | 2 +- yazi-plugin/preset/components/status.lua | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c95464c3..5e7b5809 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo - 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading. - 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer/preloader/spotter/fetcher; Just some pieces of Lua. - 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence. -- 📦 **Package Manager**: Install plugins and themes with one command, keeping them up to date, or pin them to a specific version. +- 📦 **Package Manager**: Install plugins and themes with one command, keeping them up-to-date, or pin them to a specific version. - 🧰 Integration with ripgrep, fd, fzf, zoxide - 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths - 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.) diff --git a/yazi-plugin/preset/components/status.lua b/yazi-plugin/preset/components/status.lua index 7ccca9c0..5909beb1 100644 --- a/yazi-plugin/preset/components/status.lua +++ b/yazi-plugin/preset/components/status.lua @@ -48,13 +48,11 @@ end function Status:size() local h = self._current.hovered - if not h then - return "" - end + local size = h and (h:size() or h.cha.len) or 0 local style = self:style() return ui.Line { - ui.Span(" " .. ya.readable_size(h:size() or h.cha.len) .. " "):style(style.alt), + ui.Span(" " .. ya.readable_size(size) .. " "):style(style.alt), ui.Span(th.status.sep_left.close):fg(style.alt.bg), } end From a2bbd29289c405395ba5e6151418ef2b3c8e889b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Fri, 7 Mar 2025 16:41:15 +0800 Subject: [PATCH 02/11] feat: custom search engine Lua API (#2452) --- Cargo.lock | 34 +++++++++++++++++----------------- yazi-adapter/Cargo.toml | 8 ++++---- yazi-boot/Cargo.toml | 12 ++++++------ yazi-cli/Cargo.toml | 14 +++++++------- yazi-codegen/Cargo.toml | 2 +- yazi-config/Cargo.toml | 8 ++++---- yazi-core/Cargo.toml | 24 ++++++++++++------------ yazi-dds/Cargo.toml | 10 +++++----- yazi-dds/src/sendable.rs | 30 ++++++++++++++++-------------- yazi-ffi/Cargo.toml | 4 ++-- yazi-fm/Cargo.toml | 24 ++++++++++++------------ yazi-fs/Cargo.toml | 8 ++++---- yazi-macro/Cargo.toml | 2 +- yazi-plugin/Cargo.toml | 18 +++++++++--------- yazi-plugin/src/file/file.rs | 13 +++++++++++-- yazi-plugin/src/fs/fs.rs | 9 +++++++++ yazi-plugin/src/fs/mod.rs | 2 +- yazi-plugin/src/fs/op.rs | 33 +++++++++++++++++++++++++++++++++ yazi-plugin/src/id.rs | 15 +++++++++++++++ yazi-plugin/src/lib.rs | 2 +- yazi-plugin/src/url/url.rs | 23 +++++++++++++++++++++-- yazi-plugin/src/utils/app.rs | 11 ++++++++++- yazi-plugin/src/utils/utils.rs | 1 + yazi-proxy/Cargo.toml | 8 ++++---- yazi-scheduler/Cargo.toml | 16 ++++++++-------- yazi-shared/Cargo.toml | 4 ++-- 26 files changed, 216 insertions(+), 119 deletions(-) create mode 100644 yazi-plugin/src/fs/op.rs create mode 100644 yazi-plugin/src/id.rs diff --git a/Cargo.lock b/Cargo.lock index db0466ad..817a7391 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2595,9 +2595,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.38" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb041120f25f8fbe8fd2dbe4671c7c2ed74d83be2e7a77529bf7e0790ae3f472" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", @@ -3321,7 +3321,7 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yazi-adapter" -version = "25.3.2" +version = "25.3.7" dependencies = [ "ansi-to-tui", "anyhow", @@ -3342,7 +3342,7 @@ dependencies = [ [[package]] name = "yazi-boot" -version = "25.3.2" +version = "25.3.7" dependencies = [ "clap", "clap_complete", @@ -3360,7 +3360,7 @@ dependencies = [ [[package]] name = "yazi-cli" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "clap", @@ -3383,7 +3383,7 @@ dependencies = [ [[package]] name = "yazi-codegen" -version = "25.3.2" +version = "25.3.7" dependencies = [ "quote", "syn", @@ -3391,7 +3391,7 @@ dependencies = [ [[package]] name = "yazi-config" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "bitflags 2.9.0", @@ -3411,7 +3411,7 @@ dependencies = [ [[package]] name = "yazi-core" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "bitflags 2.9.0", @@ -3445,7 +3445,7 @@ dependencies = [ [[package]] name = "yazi-dds" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "mlua", @@ -3466,7 +3466,7 @@ dependencies = [ [[package]] name = "yazi-ffi" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "core-foundation-sys", @@ -3477,7 +3477,7 @@ dependencies = [ [[package]] name = "yazi-fm" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "better-panic", @@ -3513,7 +3513,7 @@ dependencies = [ [[package]] name = "yazi-fs" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "arc-swap", @@ -3539,11 +3539,11 @@ dependencies = [ [[package]] name = "yazi-macro" -version = "25.3.2" +version = "25.3.7" [[package]] name = "yazi-plugin" -version = "25.3.2" +version = "25.3.7" dependencies = [ "ansi-to-tui", "anyhow", @@ -3584,7 +3584,7 @@ checksum = "f4b6c8e12e39ac0f79fa96f36e5b88e0da8d230691abd729eec709b43c74f632" [[package]] name = "yazi-proxy" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "mlua", @@ -3597,7 +3597,7 @@ dependencies = [ [[package]] name = "yazi-scheduler" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "async-priority-channel", @@ -3620,7 +3620,7 @@ dependencies = [ [[package]] name = "yazi-shared" -version = "25.3.2" +version = "25.3.7" dependencies = [ "anyhow", "crossterm", diff --git a/yazi-adapter/Cargo.toml b/yazi-adapter/Cargo.toml index dbe7e640..46f5aad6 100644 --- a/yazi-adapter/Cargo.toml +++ b/yazi-adapter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-adapter" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,9 +9,9 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies ansi-to-tui = { workspace = true } diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 118b2984..da9f6b59 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-boot" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,11 +9,11 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-adapter = { path = "../yazi-adapter", version = "25.3.2" } -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-adapter = { path = "../yazi-adapter", version = "25.3.7" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies clap = { workspace = true } diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index c4d8da82..302801dc 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-cli" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,11 +9,11 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-boot = { path = "../yazi-boot", version = "25.3.2" } -yazi-dds = { path = "../yazi-dds", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-boot = { path = "../yazi-boot", version = "25.3.7" } +yazi-dds = { path = "../yazi-dds", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } @@ -26,7 +26,7 @@ toml = { workspace = true } twox-hash = { workspace = true } [build-dependencies] -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External build dependencies anyhow = { workspace = true } diff --git a/yazi-codegen/Cargo.toml b/yazi-codegen/Cargo.toml index 2c755b9f..8cadb9bf 100644 --- a/yazi-codegen/Cargo.toml +++ b/yazi-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-codegen" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index 6d2ee35f..a37f6a86 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-config" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,9 +9,9 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index 1b3ced04..d7ad6db4 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-core" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,17 +9,17 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-adapter = { path = "../yazi-adapter", version = "25.3.2" } -yazi-boot = { path = "../yazi-boot", version = "25.3.2" } -yazi-codegen = { path = "../yazi-codegen", version = "25.3.2" } -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-dds = { path = "../yazi-dds", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-plugin = { path = "../yazi-plugin", version = "25.3.2" } -yazi-proxy = { path = "../yazi-proxy", version = "25.3.2" } -yazi-scheduler = { path = "../yazi-scheduler", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-adapter = { path = "../yazi-adapter", version = "25.3.7" } +yazi-boot = { path = "../yazi-boot", version = "25.3.7" } +yazi-codegen = { path = "../yazi-codegen", version = "25.3.7" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-dds = { path = "../yazi-dds", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" } +yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" } +yazi-scheduler = { path = "../yazi-scheduler", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-dds/Cargo.toml b/yazi-dds/Cargo.toml index e90ba04a..191c4453 100644 --- a/yazi-dds/Cargo.toml +++ b/yazi-dds/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-dds" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -13,10 +13,10 @@ default = [ "vendored-lua" ] vendored-lua = [ "mlua/vendored" ] [dependencies] -yazi-boot = { path = "../yazi-boot", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-boot = { path = "../yazi-boot", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-dds/src/sendable.rs b/yazi-dds/src/sendable.rs index a08727a7..a060cab8 100644 --- a/yazi-dds/src/sendable.rs +++ b/yazi-dds/src/sendable.rs @@ -43,6 +43,8 @@ impl Sendable { Value::UserData(ud) => { if let Ok(t) = ud.take::() { Data::Url(t) + } else if let Ok(t) = ud.take::() { + Data::Any(Box::new(t)) } else if let Ok(t) = ud.take::() { Data::Any(Box::new(t)) } else { @@ -72,13 +74,13 @@ impl Sendable { Value::Table(tbl) } Data::Url(u) => Value::UserData(lua.create_any_userdata(u)?), - Data::Any(a) => { - if let Ok(t) = a.downcast::() { - Value::UserData(lua.create_userdata(*t)?) - } else { - Err("unsupported userdata included".into_lua_err())? - } - } + Data::Any(a) => Value::UserData(if a.is::() { + lua.create_any_userdata(*a.downcast::().unwrap())? + } else if a.is::() { + lua.create_userdata(*a.downcast::().unwrap())? + } else { + Err("unsupported userdata included".into_lua_err())? + }), data => Self::data_to_value_ref(lua, &data)?, }) } @@ -107,13 +109,13 @@ impl Sendable { } Data::Url(u) => Value::UserData(lua.create_any_userdata(u.clone())?), Data::Bytes(b) => Value::String(lua.create_string(b)?), - Data::Any(a) => { - if let Some(t) = a.downcast_ref::() { - Value::UserData(lua.create_userdata(t.clone())?) - } else { - Err("unsupported userdata included".into_lua_err())? - } - } + Data::Any(a) => Value::UserData(if let Some(t) = a.downcast_ref::() { + lua.create_any_userdata(t.clone())? + } else if let Some(t) = a.downcast_ref::() { + lua.create_userdata(t.clone())? + } else { + Err("unsupported userdata included".into_lua_err())? + }), }) } diff --git a/yazi-ffi/Cargo.toml b/yazi-ffi/Cargo.toml index 0d7f30ec..873f6545 100644 --- a/yazi-ffi/Cargo.toml +++ b/yazi-ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-ffi" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,7 +9,7 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index 1e881cb3..4dba97a9 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-fm" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -13,17 +13,17 @@ default = [ "vendored-lua" ] vendored-lua = [ "mlua/vendored" ] [dependencies] -yazi-adapter = { path = "../yazi-adapter", version = "25.3.2" } -yazi-boot = { path = "../yazi-boot", version = "25.3.2" } -yazi-codegen = { path = "../yazi-codegen", version = "25.3.2" } -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-core = { path = "../yazi-core", version = "25.3.2" } -yazi-dds = { path = "../yazi-dds", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-plugin = { path = "../yazi-plugin", version = "25.3.2" } -yazi-proxy = { path = "../yazi-proxy", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-adapter = { path = "../yazi-adapter", version = "25.3.7" } +yazi-boot = { path = "../yazi-boot", version = "25.3.7" } +yazi-codegen = { path = "../yazi-codegen", version = "25.3.7" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-core = { path = "../yazi-core", version = "25.3.7" } +yazi-dds = { path = "../yazi-dds", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" } +yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index c3753faf..4b530916 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-fs" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,9 +9,9 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-ffi = { path = "../yazi-ffi", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-ffi = { path = "../yazi-ffi", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-macro/Cargo.toml b/yazi-macro/Cargo.toml index eb6d8347..2479458d 100644 --- a/yazi-macro/Cargo.toml +++ b/yazi-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-macro" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 9c2b9169..135d97f2 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-plugin" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -13,14 +13,14 @@ default = [ "vendored-lua" ] vendored-lua = [ "mlua/vendored" ] [dependencies] -yazi-adapter = { path = "../yazi-adapter", version = "25.3.2" } -yazi-boot = { path = "../yazi-boot", version = "25.3.2" } -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-dds = { path = "../yazi-dds", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-proxy = { path = "../yazi-proxy", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-adapter = { path = "../yazi-adapter", version = "25.3.7" } +yazi-boot = { path = "../yazi-boot", version = "25.3.7" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-dds = { path = "../yazi-dds", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies ansi-to-tui = { workspace = true } diff --git a/yazi-plugin/src/file/file.rs b/yazi-plugin/src/file/file.rs index bf2f5a8b..53a1006f 100644 --- a/yazi-plugin/src/file/file.rs +++ b/yazi-plugin/src/file/file.rs @@ -1,4 +1,4 @@ -use mlua::{AnyUserData, IntoLua, Lua, Table, UserDataRef}; +use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, Table, UserDataRef, Value}; use crate::{bindings::Cha, impl_file_fields, impl_file_methods}; @@ -29,8 +29,17 @@ impl File { } } +impl FromLua for File { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + match value { + Value::UserData(ud) => ud.take().map(Self), + _ => Err("Expected a File".into_lua_err()), + } + } +} + impl IntoLua for File { - fn into_lua(self, lua: &Lua) -> mlua::Result { + fn into_lua(self, lua: &Lua) -> mlua::Result { lua.create_any_userdata(self.0)?.into_lua(lua) } } diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index bae09a18..8419514a 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -8,6 +8,7 @@ use crate::{Composer, Error, bindings::Cha, file::File, url::{Url, UrlRef}}; pub fn compose(lua: &Lua) -> mlua::Result { Composer::make(lua, 10, |lua, key| { match key { + b"op" => op(lua)?, b"cwd" => cwd(lua)?, b"cha" => cha(lua)?, b"write" => write(lua)?, @@ -22,6 +23,14 @@ pub fn compose(lua: &Lua) -> mlua::Result { }) } +fn op(lua: &Lua) -> mlua::Result { + lua.create_function(|lua, (name, t): (mlua::String, Table)| match name.as_bytes().as_ref() { + b"part" => super::FilesOp::part(lua, t), + b"done" => super::FilesOp::done(lua, t), + _ => Err("Unknown operation".into_lua_err())?, + }) +} + fn cwd(lua: &Lua) -> mlua::Result { lua.create_function(|lua, ()| match std::env::current_dir() { Ok(p) => (Url::from(p), Value::Nil).into_lua_multi(lua), diff --git a/yazi-plugin/src/fs/mod.rs b/yazi-plugin/src/fs/mod.rs index f1b5496e..5d5a2756 100644 --- a/yazi-plugin/src/fs/mod.rs +++ b/yazi-plugin/src/fs/mod.rs @@ -1,3 +1,3 @@ #![allow(clippy::module_inception)] -yazi_macro::mod_flat!(fs); +yazi_macro::mod_flat!(fs op); diff --git a/yazi-plugin/src/fs/op.rs b/yazi-plugin/src/fs/op.rs new file mode 100644 index 00000000..31f687e2 --- /dev/null +++ b/yazi-plugin/src/fs/op.rs @@ -0,0 +1,33 @@ +use mlua::{IntoLua, Lua, Table}; + +use crate::{Id, bindings::Cha, file::File, url::Url}; + +pub(super) struct FilesOp(yazi_fs::FilesOp); + +impl FilesOp { + pub(super) fn part(_: &Lua, t: Table) -> mlua::Result { + let id: Id = t.raw_get("id")?; + let url: Url = t.raw_get("url")?; + let files: Table = t.raw_get("files")?; + + Ok(Self(yazi_fs::FilesOp::Part( + url.0, + files.sequence_values::().map(|f| f.map(|f| f.0)).collect::>>()?, + *id, + ))) + } + + pub(super) fn done(_: &Lua, t: Table) -> mlua::Result { + let id: Id = t.raw_get("id")?; + let cha: Cha = t.raw_get("cha")?; + let url: Url = t.raw_get("url")?; + + Ok(Self(yazi_fs::FilesOp::Done(url.0, *cha, *id))) + } +} + +impl IntoLua for FilesOp { + fn into_lua(self, lua: &Lua) -> mlua::Result { + lua.create_any_userdata(self.0)?.into_lua(lua) + } +} diff --git a/yazi-plugin/src/id.rs b/yazi-plugin/src/id.rs new file mode 100644 index 00000000..8f14e4d4 --- /dev/null +++ b/yazi-plugin/src/id.rs @@ -0,0 +1,15 @@ +use std::ops::Deref; + +use mlua::{FromLua, UserData}; + +#[derive(Clone, Copy, FromLua)] +pub struct Id(pub yazi_shared::Id); + +impl Deref for Id { + type Target = yazi_shared::Id; + + #[inline] + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl UserData for Id {} diff --git a/yazi-plugin/src/lib.rs b/yazi-plugin/src/lib.rs index ca4bb83a..9cbf8e96 100644 --- a/yazi-plugin/src/lib.rs +++ b/yazi-plugin/src/lib.rs @@ -4,7 +4,7 @@ mod macros; yazi_macro::mod_pub!(bindings config elements external file fs isolate loader process pubsub url utils); -yazi_macro::mod_flat!(clipboard composer error lua runtime); +yazi_macro::mod_flat!(clipboard composer error id lua runtime); pub fn init() -> anyhow::Result<()> { CLIPBOARD.with(<_>::default); diff --git a/yazi-plugin/src/url/url.rs b/yazi-plugin/src/url/url.rs index e497d50a..23ec448f 100644 --- a/yazi-plugin/src/url/url.rs +++ b/yazi-plugin/src/url/url.rs @@ -1,4 +1,4 @@ -use mlua::{ExternalError, IntoLua, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef, Value}; +use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef, Value}; pub type UrlRef = UserDataRef; @@ -54,6 +54,10 @@ impl Url { Ok(path.ok().map(Self::from)) }); + reg.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| { + Ok(Self(ud.take::()?.into_search(&frag.to_str()?))) + }); + reg.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me == &*other)); reg.add_meta_method(MetaMethod::ToString, |lua, me, ()| { lua.create_string(me.as_os_str().as_encoded_bytes()) @@ -67,7 +71,13 @@ impl Url { pub fn install(lua: &Lua) -> mlua::Result<()> { lua.globals().raw_set( "Url", - lua.create_function(|_, url: mlua::String| Ok(Self::from(url.to_str()?.as_ref())))?, + lua.create_function(|_, value: Value| { + Ok(match value { + Value::String(s) => Self::from(s.to_str()?.as_ref()), + Value::UserData(ud) => Self(ud.borrow::()?.clone()), + _ => Err("Expected a string or a Url".into_lua_err())?, + }) + })?, ) } } @@ -76,6 +86,15 @@ impl> From for Url { fn from(value: T) -> Self { Self(value.into()) } } +impl FromLua for Url { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + match value { + Value::UserData(ud) => ud.take().map(Self), + _ => Err("Expected a Url".into_lua_err()), + } + } +} + impl IntoLua for Url { fn into_lua(self, lua: &Lua) -> mlua::Result { lua.create_any_userdata(self.0)?.into_lua(lua) diff --git a/yazi-plugin/src/utils/app.rs b/yazi-plugin/src/utils/app.rs index 154f4064..dee784b8 100644 --- a/yazi-plugin/src/utils/app.rs +++ b/yazi-plugin/src/utils/app.rs @@ -2,9 +2,18 @@ use mlua::{AnyUserData, ExternalError, Function, Lua}; use yazi_proxy::{AppProxy, HIDER}; use super::Utils; -use crate::bindings::{Permit, PermitRef}; +use crate::{Id, bindings::{Permit, PermitRef}}; impl Utils { + pub(super) fn id(lua: &Lua) -> mlua::Result { + lua.create_function(|_, type_: mlua::String| { + Ok(Id(match type_.as_bytes().as_ref() { + b"ft" => yazi_fs::FILES_TICKET.next(), + _ => Err("Invalid id type".into_lua_err())?, + })) + }) + } + pub(super) fn hide(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, ()| async move { if lua.named_registry_value::>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) { diff --git a/yazi-plugin/src/utils/utils.rs b/yazi-plugin/src/utils/utils.rs index 51ef6dff..ff27922f 100644 --- a/yazi-plugin/src/utils/utils.rs +++ b/yazi-plugin/src/utils/utils.rs @@ -8,6 +8,7 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result { Composer::make(lua, 45, move |lua, key| { match key { // App + b"id" => Utils::id(lua)?, b"hide" => Utils::hide(lua)?, // Cache diff --git a/yazi-proxy/Cargo.toml b/yazi-proxy/Cargo.toml index 4abe5717..40b77b6c 100644 --- a/yazi-proxy/Cargo.toml +++ b/yazi-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-proxy" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -13,9 +13,9 @@ default = [ "vendored-lua" ] vendored-lua = [ "mlua/vendored" ] [dependencies] -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 555369e2..85958a41 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-scheduler" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -9,13 +9,13 @@ homepage = "https://yazi-rs.github.io" repository = "https://github.com/sxyazi/yazi" [dependencies] -yazi-config = { path = "../yazi-config", version = "25.3.2" } -yazi-dds = { path = "../yazi-dds", version = "25.3.2" } -yazi-fs = { path = "../yazi-fs", version = "25.3.2" } -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } -yazi-plugin = { path = "../yazi-plugin", version = "25.3.2" } -yazi-proxy = { path = "../yazi-proxy", version = "25.3.2" } -yazi-shared = { path = "../yazi-shared", version = "25.3.2" } +yazi-config = { path = "../yazi-config", version = "25.3.7" } +yazi-dds = { path = "../yazi-dds", version = "25.3.7" } +yazi-fs = { path = "../yazi-fs", version = "25.3.7" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } +yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" } +yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" } +yazi-shared = { path = "../yazi-shared", version = "25.3.7" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index e3316b01..67f23ef5 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "yazi-shared" -version = "25.3.2" +version = "25.3.7" edition = "2021" license = "MIT" authors = [ "sxyazi " ] @@ -10,7 +10,7 @@ repository = "https://github.com/sxyazi/yazi" rust-version = "1.83.0" [dependencies] -yazi-macro = { path = "../yazi-macro", version = "25.3.2" } +yazi-macro = { path = "../yazi-macro", version = "25.3.7" } # External dependencies anyhow = { workspace = true } From ece635d6b899a90315b7175db071fe4d902fa5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sat, 8 Mar 2025 09:50:38 +0800 Subject: [PATCH 03/11] feat: allow `tab_swap` to cycle tabs (#2456) --- yazi-core/src/mgr/commands/tab_close.rs | 2 +- yazi-core/src/mgr/commands/tab_swap.rs | 2 +- yazi-core/src/mgr/commands/tab_switch.rs | 2 +- yazi-core/src/mgr/tabs.rs | 8 -------- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/yazi-core/src/mgr/commands/tab_close.rs b/yazi-core/src/mgr/commands/tab_close.rs index 0f699b61..59cc62df 100644 --- a/yazi-core/src/mgr/commands/tab_close.rs +++ b/yazi-core/src/mgr/commands/tab_close.rs @@ -27,7 +27,7 @@ impl Tabs { if opt.idx > self.cursor { self.set_idx(self.cursor); } else { - self.set_idx(self.absolute(1)); + self.set_idx(usize::min(self.cursor + 1, self.items.len() - 1)); } render!(); diff --git a/yazi-core/src/mgr/commands/tab_swap.rs b/yazi-core/src/mgr/commands/tab_swap.rs index 33888ec2..11c111ca 100644 --- a/yazi-core/src/mgr/commands/tab_swap.rs +++ b/yazi-core/src/mgr/commands/tab_swap.rs @@ -14,7 +14,7 @@ impl From for Opt { impl Tabs { #[yazi_codegen::command] pub fn swap(&mut self, opt: Opt) { - let idx = self.absolute(opt.step); + let idx = opt.step.saturating_add_unsigned(self.cursor).rem_euclid(self.items.len() as _) as _; if idx == self.cursor { return; } diff --git a/yazi-core/src/mgr/commands/tab_switch.rs b/yazi-core/src/mgr/commands/tab_switch.rs index 9bb6c78c..c174ebdf 100644 --- a/yazi-core/src/mgr/commands/tab_switch.rs +++ b/yazi-core/src/mgr/commands/tab_switch.rs @@ -18,7 +18,7 @@ impl Tabs { #[yazi_codegen::command] pub fn switch(&mut self, opt: Opt) { let idx = if opt.relative { - (self.cursor as isize + opt.step).rem_euclid(self.items.len() as isize) as usize + opt.step.saturating_add_unsigned(self.cursor).rem_euclid(self.items.len() as _) as _ } else { opt.step as usize }; diff --git a/yazi-core/src/mgr/tabs.rs b/yazi-core/src/mgr/tabs.rs index 5e9e4875..9c53e657 100644 --- a/yazi-core/src/mgr/tabs.rs +++ b/yazi-core/src/mgr/tabs.rs @@ -29,14 +29,6 @@ impl Tabs { tabs } - pub(super) fn absolute(&self, rel: isize) -> usize { - if rel > 0 { - (self.cursor + rel as usize).min(self.items.len() - 1) - } else { - self.cursor.saturating_sub(rel.unsigned_abs()) - } - } - pub(super) fn set_idx(&mut self, idx: usize) { // Reset the preview of the last active tab if let Some(active) = self.items.get_mut(self.cursor) { From 31177fd1fd71500e6a30edee60a122f78e5e7424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sat, 8 Mar 2025 13:52:29 +0800 Subject: [PATCH 04/11] fix: remove the temporary extraction directory forcefully (#2458) --- yazi-plugin/preset/plugins/extract.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yazi-plugin/preset/plugins/extract.lua b/yazi-plugin/preset/plugins/extract.lua index e10a937c..5775034d 100644 --- a/yazi-plugin/preset/plugins/extract.lua +++ b/yazi-plugin/preset/plugins/extract.lua @@ -57,7 +57,7 @@ function M:try_with(from, pwd, to) local output, err = child:wait_with_output() if output and output.status.code == 2 and archive.is_encrypted(output.stderr) then - fs.remove("dir_clean", tmp) + fs.remove("dir_all", tmp) return true -- Need to retry end From 119cc9c2a1392e49a5684de934330d981555bccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sun, 9 Mar 2025 14:53:55 +0800 Subject: [PATCH 05/11] feat: support using Yazi in Helix directly without Zellij or tmux (#2461) --- Cargo.lock | 6 +- Cargo.toml | 2 +- cspell.json | 2 +- yazi-adapter/Cargo.toml | 6 - yazi-adapter/src/brand.rs | 3 + yazi-adapter/src/drivers/kgp_old.rs | 9 +- yazi-adapter/src/emulator.rs | 32 ++-- yazi-adapter/src/lib.rs | 2 +- yazi-adapter/src/mux.rs | 3 +- yazi-adapter/src/stdin.rs | 174 ---------------------- yazi-boot/src/actions/debug.rs | 3 +- yazi-config/src/lib.rs | 14 +- yazi-core/src/mgr/commands/bulk_rename.rs | 40 +++-- yazi-core/src/mgr/commands/refresh.rs | 4 +- yazi-core/src/tasks/commands/inspect.rs | 20 ++- yazi-fm/src/app/commands/render.rs | 12 +- yazi-fm/src/term.rs | 36 ++--- yazi-plugin/src/clipboard.rs | 5 +- yazi-shared/Cargo.toml | 2 +- yazi-shared/src/lib.rs | 4 +- yazi-shared/src/os.rs | 4 +- yazi-shared/src/terminal.rs | 12 +- yazi-shared/src/tty/handle.rs | 164 ++++++++++++++++++++ yazi-shared/src/tty/mod.rs | 5 + yazi-shared/src/tty/tty.rs | 74 +++++++++ 25 files changed, 354 insertions(+), 284 deletions(-) delete mode 100644 yazi-adapter/src/stdin.rs create mode 100644 yazi-shared/src/tty/handle.rs create mode 100644 yazi-shared/src/tty/mod.rs create mode 100644 yazi-shared/src/tty/tty.rs diff --git a/Cargo.lock b/Cargo.lock index 817a7391..ea6e69ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 78ee656a..2ea36e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/cspell.json b/cspell.json index a3b63c4f..41b40792 100644 --- a/cspell.json +++ b/cspell.json @@ -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"} \ No newline at end of file +{"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"]} \ No newline at end of file diff --git a/yazi-adapter/Cargo.toml b/yazi-adapter/Cargo.toml index 46f5aad6..3188a5ff 100644 --- a/yazi-adapter/Cargo.toml +++ b/yazi-adapter/Cargo.toml @@ -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" ] } diff --git a/yazi-adapter/src/brand.rs b/yazi-adapter/src/brand.rs index f9f6bef6..100a11a1 100644 --- a/yazi-adapter/src/brand.rs +++ b/yazi-adapter/src/brand.rs @@ -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], diff --git a/yazi-adapter/src/drivers/kgp_old.rs b/yazi-adapter/src/drivers/kgp_old.rs index 1d5259f7..f149bf8a 100644 --- a/yazi-adapter/src/drivers/kgp_old.rs +++ b/yazi-adapter/src/drivers/kgp_old.rs @@ -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(()) } diff --git a/yazi-adapter/src/emulator.rs b/yazi-adapter/src/emulator.rs index 6b258387..e8119808 100644 --- a/yazi-adapter/src/emulator.rs +++ b/yazi-adapter/src/emulator.rs @@ -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((x, y): (u16, u16), cb: F) -> Result where - F: FnOnce(&mut std::io::BufWriter) -> Result, + F: FnOnce(&mut 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()); + 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")) }); diff --git a/yazi-adapter/src/lib.rs b/yazi-adapter/src/lib.rs index d931bc84..42da957c 100644 --- a/yazi-adapter/src/lib.rs +++ b/yazi-adapter/src/lib.rs @@ -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}; diff --git a/yazi-adapter/src/mux.rs b/yazi-adapter/src/mux.rs index 2eb2c1ae..a08acc79 100644 --- a/yazi-adapter/src/mux.rs +++ b/yazi-adapter/src/mux.rs @@ -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(()) diff --git a/yazi-adapter/src/stdin.rs b/yazi-adapter/src/stdin.rs deleted file mode 100644 index 8cf9ca95..00000000 --- a/yazi-adapter/src/stdin.rs +++ /dev/null @@ -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

(&mut self, timeout: Duration, predicate: P) -> (Vec, std::io::Result<()>) - where - P: Fn(u8, &[u8]) -> bool, - { - let mut buf: Vec = 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 = "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 }) - } - } -} diff --git a/yazi-boot/src/actions/debug.rs b/yazi-boot/src/actions/debug.rs index df451734..1659964a 100644 --- a/yazi-boot/src/actions/debug.rs +++ b/yazi-boot/src/actions/debug.rs @@ -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"))?; diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index ff25f37e..7cb515aa 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -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 = RoCell::new(); pub static MGR: RoCell = 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 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(()) } diff --git a/yazi-core/src/mgr/commands/bulk_rename.rs b/yazi-core/src/mgr/commands/bulk_rename.rs index fe53ba50..1376a361 100644 --- a/yazi-core/src/mgr/commands/bulk_rename.rs +++ b/yazi-core/src/mgr/commands/bulk_rename.rs @@ -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, new: Vec) -> 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(()) } diff --git a/yazi-core/src/mgr/commands/refresh.rs b/yazi-core/src/mgr/commands/refresh.rs index 45ceeae0..b2d721ca 100644 --- a/yazi-core/src/mgr/commands/refresh.rs +++ b/yazi-core/src/mgr/commands/refresh.rs @@ -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(); diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-core/src/tasks/commands/inspect.rs index 147c4381..f3003e13 100644 --- a/yazi-core/src/tasks/commands/inspect.rs +++ b/yazi-core/src/tasks/commands/inspect.rs @@ -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; } }, diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/commands/render.rs index 1361a4ae..97a80c69 100644 --- a/yazi-fm/src/app/commands/render.rs +++ b/yazi-fm/src/app/commands/render.rs @@ -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(); diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs index c1539bdc..c73a3441 100644 --- a/yazi-fm/src/term.rs +++ b/yazi-fm/src/term.rs @@ -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>>, + inner: Terminal>>, last_area: Rect, last_buffer: Buffer, } @@ -22,7 +22,7 @@ impl Term { pub(super) fn start() -> Result { static SKIP: SyncCell = 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>>; + type Target = Terminal>>; fn deref(&self) -> &Self::Target { &self.inner } } diff --git a/yazi-plugin/src/clipboard.rs b/yazi-plugin/src/clipboard.rs index 1a51deee..a5d3642c 100644 --- a/yazi-plugin/src/clipboard.rs +++ b/yazi-plugin/src/clipboard.rs @@ -55,13 +55,14 @@ impl Clipboard { #[cfg(unix)] pub async fn set(&self, s: impl AsRef) { - 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", &[][..]), diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 67f23ef5..46550e43 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -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" ] } diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 7588f037..ca4260ab 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -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)] diff --git a/yazi-shared/src/os.rs b/yazi-shared/src/os.rs index b6c76c4b..fa9f213a 100644 --- a/yazi-shared/src/os.rs +++ b/yazi-shared/src/os.rs @@ -10,8 +10,6 @@ pub fn hostname() -> Option<&'static str> { #[cfg(unix)] fn hostname_impl() -> Result { - use std::io::{Error, ErrorKind}; - use libc::{gethostname, strlen}; let mut s = [0; 256]; @@ -24,6 +22,6 @@ fn hostname_impl() -> Result { }; 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()) } diff --git a/yazi-shared/src/terminal.rs b/yazi-shared/src/terminal.rs index ed66d0cc..c874a7fc 100644 --- a/yazi-shared/src/terminal.rs +++ b/yazi-shared/src/terminal.rs @@ -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") + ) } diff --git a/yazi-shared/src/tty/handle.rs b/yazi-shared/src/tty/handle.rs new file mode 100644 index 00000000..23544fd1 --- /dev/null +++ b/yazi-shared/src/tty/handle.rs @@ -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 { + #[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 { + #[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 { + 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 { + 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 { + 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 { + 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 = 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 { + 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 { + 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) + } +} diff --git a/yazi-shared/src/tty/mod.rs b/yazi-shared/src/tty/mod.rs new file mode 100644 index 00000000..9a54aebc --- /dev/null +++ b/yazi-shared/src/tty/mod.rs @@ -0,0 +1,5 @@ +yazi_macro::mod_flat!(tty handle); + +pub static TTY: crate::RoCell = crate::RoCell::new(); + +pub(super) fn init() { TTY.with(<_>::default); } diff --git a/yazi-shared/src/tty/tty.rs b/yazi-shared/src/tty/tty.rs new file mode 100644 index 00000000..74690348 --- /dev/null +++ b/yazi-shared/src/tty/tty.rs @@ -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, + stdout: Mutex>, +} + +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 { self.stdin.lock() } + + pub fn lockout(&self) -> MutexGuard> { self.stdout.lock() } + + pub fn read_until

(&self, timeout: Duration, predicate: P) -> (Vec, std::io::Result<()>) + where + P: Fn(u8, &[u8]) -> bool, + { + let mut buf: Vec = 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); + +impl Read for TtyReader<'_> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.lock().read(buf) } +} + +// --- Writer +pub struct TtyWriter<'a>(&'a Mutex>); + +impl Write for TtyWriter<'_> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0.lock().write(buf) } + + fn flush(&mut self) -> std::io::Result<()> { self.0.lock().flush() } +} From 22980cf00018013fb6a688dc5385fb38868bd7a4 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sun, 9 Mar 2025 19:08:01 +0800 Subject: [PATCH 06/11] fix: set terminal code page to UTF-8 on Windows --- yazi-shared/Cargo.toml | 2 +- yazi-shared/src/tty/tty.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 46550e43..8c80fa90 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -28,7 +28,7 @@ libc = { workspace = true } uzers = { workspace = true } [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_IO", "Win32_System_Threading", "Win32_UI_Shell" ] } +windows-sys = { version = "0.59.0", features = [ "Win32_Globalization", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Threading", "Win32_UI_Shell" ] } [target.'cfg(target_os = "macos")'.dependencies] crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } diff --git a/yazi-shared/src/tty/tty.rs b/yazi-shared/src/tty/tty.rs index 74690348..733877a5 100644 --- a/yazi-shared/src/tty/tty.rs +++ b/yazi-shared/src/tty/tty.rs @@ -11,6 +11,9 @@ pub struct Tty { impl Default for Tty { fn default() -> Self { + #[cfg(windows)] + Self::set_code_page().expect("failed to set terminal code page"); + 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)) } @@ -55,6 +58,13 @@ impl Tty { let result = read(); (buf, result) } + + #[cfg(windows)] + fn set_code_page() -> std::io::Result<()> { + use windows_sys::Win32::{Globalization::CP_UTF8, System::Console::SetConsoleOutputCP}; + + if unsafe { SetConsoleOutputCP(CP_UTF8) } == 0 { Err(Error::last_os_error()) } else { Ok(()) } + } } // --- Reader From c8bf2c507a1ff4b4051d018833b659edc3ddf637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Mon, 10 Mar 2025 14:41:02 +0800 Subject: [PATCH 07/11] refactor: prefer `WriteConsoleW` for Windows console output (#2464) --- Cargo.lock | 31 ++--- Cargo.toml | 2 +- cspell.json | 2 +- yazi-adapter/src/drivers/chafa.rs | 12 +- yazi-adapter/src/drivers/iip.rs | 10 +- yazi-adapter/src/drivers/kgp.rs | 14 +- yazi-adapter/src/drivers/kgp_old.rs | 4 +- yazi-adapter/src/drivers/sixel.rs | 10 +- yazi-codegen/Cargo.toml | 2 +- yazi-core/src/mgr/commands/bulk_rename.rs | 4 +- yazi-shared/src/lib.rs | 2 +- yazi-shared/src/tty/handle.rs | 33 +++-- yazi-shared/src/tty/mod.rs | 5 +- yazi-shared/src/tty/tty.rs | 10 -- yazi-shared/src/tty/windows.rs | 160 ++++++++++++++++++++++ yazi-shared/src/utf8.rs | 53 +++++++ 16 files changed, 283 insertions(+), 71 deletions(-) create mode 100644 yazi-shared/src/tty/windows.rs create mode 100644 yazi-shared/src/utf8.rs diff --git a/Cargo.lock b/Cargo.lock index ea6e69ef..61caa028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,12 +299,6 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "byteorder-lite" version = "0.1.0" @@ -1913,9 +1907,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -2252,9 +2246,9 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -2271,9 +2265,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -2452,9 +2446,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -3661,19 +3655,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2ea36e1a..357b80be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ parking_lot = "0.12.3" ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] } regex = "1.11.1" scopeguard = "1.2.0" -serde = { version = "1.0.218", features = [ "derive" ] } +serde = { version = "1.0.219", features = [ "derive" ] } serde_json = "1.0.140" tokio = { version = "1.44.0", features = [ "full" ] } tokio-stream = "0.1.17" diff --git a/cspell.json b/cspell.json index 41b40792..3216a2e4 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"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"]} \ No newline at end of file +{"version":"0.2","language":"en","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","rposition"],"flagWords":[]} \ No newline at end of file diff --git a/yazi-adapter/src/drivers/chafa.rs b/yazi-adapter/src/drivers/chafa.rs index 5c9131d4..b8c18b56 100644 --- a/yazi-adapter/src/drivers/chafa.rs +++ b/yazi-adapter/src/drivers/chafa.rs @@ -55,10 +55,10 @@ impl Chafa { Adapter::Chafa.image_hide()?; Adapter::shown_store(area); - Emulator::move_lock((max.x, max.y), |stderr| { + Emulator::move_lock((max.x, max.y), |w| { for (i, line) in lines.into_iter().enumerate() { - stderr.write_all(line)?; - queue!(stderr, MoveTo(max.x, max.y + i as u16 + 1))?; + w.write_all(line)?; + queue!(w, MoveTo(max.x, max.y + i as u16 + 1))?; } Ok(area) }) @@ -66,10 +66,10 @@ impl Chafa { pub(crate) fn image_erase(area: Rect) -> Result<()> { let s = " ".repeat(area.width as usize); - Emulator::move_lock((0, 0), |stderr| { + Emulator::move_lock((0, 0), |w| { for y in area.top()..area.bottom() { - queue!(stderr, MoveTo(area.x, y))?; - write!(stderr, "{s}")?; + queue!(w, MoveTo(area.x, y))?; + write!(w, "{s}")?; } Ok(()) }) diff --git a/yazi-adapter/src/drivers/iip.rs b/yazi-adapter/src/drivers/iip.rs index 967adc7c..1f8d6528 100644 --- a/yazi-adapter/src/drivers/iip.rs +++ b/yazi-adapter/src/drivers/iip.rs @@ -19,18 +19,18 @@ impl Iip { Adapter::Iip.image_hide()?; Adapter::shown_store(area); - Emulator::move_lock((max.x, max.y), |stderr| { - stderr.write_all(&b)?; + Emulator::move_lock((max.x, max.y), |w| { + w.write_all(&b)?; Ok(area) }) } pub(crate) fn image_erase(area: Rect) -> Result<()> { let s = " ".repeat(area.width as usize); - Emulator::move_lock((0, 0), |stderr| { + Emulator::move_lock((0, 0), |w| { for y in area.top()..area.bottom() { - queue!(stderr, MoveTo(area.x, y))?; - write!(stderr, "{s}")?; + queue!(w, MoveTo(area.x, y))?; + write!(w, "{s}")?; } Ok(()) }) diff --git a/yazi-adapter/src/drivers/kgp.rs b/yazi-adapter/src/drivers/kgp.rs index 051ca123..1290cf35 100644 --- a/yazi-adapter/src/drivers/kgp.rs +++ b/yazi-adapter/src/drivers/kgp.rs @@ -321,22 +321,22 @@ impl Kgp { Adapter::Kgp.image_hide()?; Adapter::shown_store(area); - Emulator::move_lock((area.x, area.y), |stderr| { - stderr.write_all(&b1)?; - stderr.write_all(&b2)?; + Emulator::move_lock((area.x, area.y), |w| { + w.write_all(&b1)?; + w.write_all(&b2)?; Ok(area) }) } pub(crate) fn image_erase(area: Rect) -> Result<()> { let s = " ".repeat(area.width as usize); - Emulator::move_lock((0, 0), |stderr| { + Emulator::move_lock((0, 0), |w| { for y in area.top()..area.bottom() { - queue!(stderr, MoveTo(area.x, y))?; - write!(stderr, "{s}")?; + queue!(w, MoveTo(area.x, y))?; + write!(w, "{s}")?; } - write!(stderr, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?; + write!(w, "{START}_Gq=2,a=d,d=A{ESCAPE}\\{CLOSE}")?; Ok(()) }) } diff --git a/yazi-adapter/src/drivers/kgp_old.rs b/yazi-adapter/src/drivers/kgp_old.rs index f149bf8a..e21bc6fa 100644 --- a/yazi-adapter/src/drivers/kgp_old.rs +++ b/yazi-adapter/src/drivers/kgp_old.rs @@ -19,8 +19,8 @@ impl KgpOld { Adapter::KgpOld.image_hide()?; Adapter::shown_store(area); - Emulator::move_lock((area.x, area.y), |stderr| { - stderr.write_all(&b)?; + Emulator::move_lock((area.x, area.y), |w| { + w.write_all(&b)?; Ok(area) }) } diff --git a/yazi-adapter/src/drivers/sixel.rs b/yazi-adapter/src/drivers/sixel.rs index 56a30993..526341ca 100644 --- a/yazi-adapter/src/drivers/sixel.rs +++ b/yazi-adapter/src/drivers/sixel.rs @@ -19,18 +19,18 @@ impl Sixel { Adapter::Sixel.image_hide()?; Adapter::shown_store(area); - Emulator::move_lock((area.x, area.y), |stderr| { - stderr.write_all(&b)?; + Emulator::move_lock((area.x, area.y), |w| { + w.write_all(&b)?; Ok(area) }) } pub(crate) fn image_erase(area: Rect) -> Result<()> { let s = " ".repeat(area.width as usize); - Emulator::move_lock((0, 0), |stderr| { + Emulator::move_lock((0, 0), |w| { for y in area.top()..area.bottom() { - queue!(stderr, MoveTo(area.x, y))?; - write!(stderr, "{s}")?; + queue!(w, MoveTo(area.x, y))?; + write!(w, "{s}")?; } Ok(()) }) diff --git a/yazi-codegen/Cargo.toml b/yazi-codegen/Cargo.toml index 8cadb9bf..0e632caf 100644 --- a/yazi-codegen/Cargo.toml +++ b/yazi-codegen/Cargo.toml @@ -13,5 +13,5 @@ proc-macro = true [dependencies] # External dependencies -syn = { version = "2.0.99", features = [ "full" ] } +syn = { version = "2.0.100", features = [ "full" ] } quote = "1.0.39" diff --git a/yazi-core/src/mgr/commands/bulk_rename.rs b/yazi-core/src/mgr/commands/bulk_rename.rs index 1376a361..c30a8302 100644 --- a/yazi-core/src/mgr/commands/bulk_rename.rs +++ b/yazi-core/src/mgr/commands/bulk_rename.rs @@ -66,8 +66,8 @@ impl Mgr { { let mut w = TTY.lockout(); - for (o, n) in &todo { - writeln!(w, "{} -> {}", o.display(), n.display())?; + for (old, new) in &todo { + writeln!(w, "{} -> {}", old.display(), new.display())?; } write!(w, "Continue to rename? (y/N): ")?; w.flush()?; diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index ca4260ab..abc04594 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -2,7 +2,7 @@ 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); +yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time utf8); pub fn init() { tty::init(); diff --git a/yazi-shared/src/tty/handle.rs b/yazi-shared/src/tty/handle.rs index 23544fd1..7a54936f 100644 --- a/yazi-shared/src/tty/handle.rs +++ b/yazi-shared/src/tty/handle.rs @@ -2,10 +2,14 @@ use std::{io::{Error, ErrorKind, Read, Write}, time::Duration}; pub struct Handle { #[cfg(unix)] - inner: std::os::fd::RawFd, + inner: std::os::fd::RawFd, #[cfg(windows)] - inner: std::os::windows::io::RawHandle, - close: bool, + inner: std::os::windows::io::RawHandle, + close: bool, + #[cfg(windows)] + out_utf8: bool, + #[cfg(windows)] + incomplete_utf8: super::IncompleteUtf8, } impl Drop for Handle { @@ -49,16 +53,20 @@ impl Write for Handle { 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(); + _ = 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 + if self.out_utf8 { + let mut f = unsafe { std::fs::File::from_raw_handle(self.inner) }; + let result = f.write(buf); + _ = f.into_raw_handle(); + result + } else { + super::write_console_utf16(buf, &mut self.incomplete_utf8, self.inner) + } } } @@ -114,7 +122,7 @@ impl Handle { #[cfg(windows)] impl Handle { pub(super) fn new(out: bool) -> std::io::Result { - use windows_sys::Win32::{Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, Storage::FileSystem::{CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}}; + use windows_sys::Win32::{Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, Globalization::CP_UTF8, Storage::FileSystem::{CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}, System::Console::GetConsoleOutputCP}; let name: Vec = if out { "CONOUT$\0" } else { "CONIN$\0" }.encode_utf16().collect(); let result = unsafe { @@ -132,7 +140,12 @@ impl Handle { if result == INVALID_HANDLE_VALUE { Err(std::io::Error::last_os_error()) } else { - Ok(Self { inner: result, close: true }) + Ok(Self { + inner: result, + close: true, + out_utf8: unsafe { GetConsoleOutputCP() } == CP_UTF8, + incomplete_utf8: Default::default(), + }) } } diff --git a/yazi-shared/src/tty/mod.rs b/yazi-shared/src/tty/mod.rs index 9a54aebc..faef2c9c 100644 --- a/yazi-shared/src/tty/mod.rs +++ b/yazi-shared/src/tty/mod.rs @@ -1,4 +1,7 @@ -yazi_macro::mod_flat!(tty handle); +yazi_macro::mod_flat!(handle tty); + +#[cfg(windows)] +yazi_macro::mod_flat!(windows); pub static TTY: crate::RoCell = crate::RoCell::new(); diff --git a/yazi-shared/src/tty/tty.rs b/yazi-shared/src/tty/tty.rs index 733877a5..74690348 100644 --- a/yazi-shared/src/tty/tty.rs +++ b/yazi-shared/src/tty/tty.rs @@ -11,9 +11,6 @@ pub struct Tty { impl Default for Tty { fn default() -> Self { - #[cfg(windows)] - Self::set_code_page().expect("failed to set terminal code page"); - 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)) } @@ -58,13 +55,6 @@ impl Tty { let result = read(); (buf, result) } - - #[cfg(windows)] - fn set_code_page() -> std::io::Result<()> { - use windows_sys::Win32::{Globalization::CP_UTF8, System::Console::SetConsoleOutputCP}; - - if unsafe { SetConsoleOutputCP(CP_UTF8) } == 0 { Err(Error::last_os_error()) } else { Ok(()) } - } } // --- Reader diff --git a/yazi-shared/src/tty/windows.rs b/yazi-shared/src/tty/windows.rs new file mode 100644 index 00000000..72d8c2bd --- /dev/null +++ b/yazi-shared/src/tty/windows.rs @@ -0,0 +1,160 @@ +// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/windows/stdio.rs + +use std::{mem::MaybeUninit, os::windows::io::RawHandle, str}; + +use windows_sys::Win32::{Globalization::{CP_UTF8, MB_ERR_INVALID_CHARS, MultiByteToWideChar}, System::Console::WriteConsoleW}; + +use crate::{floor_char_boundary, utf8_char_width}; + +// Apparently Windows doesn't handle large reads on stdin or writes to +// stdout/stderr well (see #13304 for details). +// +// From MSDN (2011): "The storage for this buffer is allocated from a shared +// heap for the process that is 64 KB in size. The maximum size of the buffer +// will depend on heap usage." +// +// We choose the cap at 8 KiB because libuv does the same, and it seems to be +// acceptable so far. +const MAX_BUFFER_SIZE: usize = 8192; + +#[derive(Default)] +pub(super) struct IncompleteUtf8 { + bytes: [u8; 4], + len: u8, +} + +pub(super) fn write_console_utf16( + data: &[u8], + incomplete_utf8: &mut IncompleteUtf8, + handle: RawHandle, +) -> std::io::Result { + if incomplete_utf8.len > 0 { + assert!(incomplete_utf8.len < 4, "Unexpected number of bytes for incomplete UTF-8 codepoint."); + if data[0] >> 6 != 0b10 { + // not a continuation byte - reject + incomplete_utf8.len = 0; + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", + )); + } + incomplete_utf8.bytes[incomplete_utf8.len as usize] = data[0]; + incomplete_utf8.len += 1; + let char_width = utf8_char_width(incomplete_utf8.bytes[0]); + if (incomplete_utf8.len as usize) < char_width { + // more bytes needed + return Ok(1); + } + let s = str::from_utf8(&incomplete_utf8.bytes[0..incomplete_utf8.len as usize]); + incomplete_utf8.len = 0; + match s { + Ok(s) => { + assert_eq!(char_width, s.len()); + let written = write_valid_utf8_to_console(handle, s)?; + assert_eq!(written, s.len()); // guaranteed by write_valid_utf8_to_console() for single codepoint writes + return Ok(1); + } + Err(_) => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", + )); + } + } + } + + // As the console is meant for presenting text, we assume bytes of `data` are + // encoded as UTF-8, which needs to be encoded as UTF-16. + // + // If the data is not valid UTF-8 we write out as many bytes as are valid. + // If the first byte is invalid it is either first byte of a multi-byte sequence + // but the provided byte slice is too short or it is the first byte of an + // invalid multi-byte sequence. + let len = std::cmp::min(data.len(), MAX_BUFFER_SIZE / 2); + let utf8 = match str::from_utf8(&data[..len]) { + Ok(s) => s, + Err(ref e) if e.valid_up_to() == 0 => { + let first_byte_char_width = utf8_char_width(data[0]); + if first_byte_char_width > 1 && data.len() < first_byte_char_width { + incomplete_utf8.bytes[0] = data[0]; + incomplete_utf8.len = 1; + return Ok(1); + } else { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Windows stdio in console mode does not support writing non-UTF-8 byte sequences", + )); + } + } + Err(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(), + }; + + write_valid_utf8_to_console(handle, utf8) +} + +fn write_valid_utf8_to_console(handle: RawHandle, utf8: &str) -> std::io::Result { + debug_assert!(!utf8.is_empty()); + + let mut utf16 = [MaybeUninit::::uninit(); MAX_BUFFER_SIZE / 2]; + let utf8 = &utf8[..floor_char_boundary(utf8, utf16.len())]; + + let utf16: &[u16] = unsafe { + // Note that this theoretically checks validity twice in the (most common) case + // where the underlying byte sequence is valid utf-8 (given the check in + // `write()`). + let result = MultiByteToWideChar( + CP_UTF8, + MB_ERR_INVALID_CHARS, + utf8.as_ptr(), + utf8.len() as i32, + utf16.as_mut_ptr() as *mut _, + utf16.len() as i32, + ); + assert!(result != 0, "Unexpected error in MultiByteToWideChar"); + + // Safety: MultiByteToWideChar initializes `result` values. + &*(&utf16[..result as usize] as *const [MaybeUninit] as *const [u16]) + }; + + let mut written = write_u16s(handle, utf16)?; + + // Figure out how many bytes of as UTF-8 were written away as UTF-16. + if written == utf16.len() { + Ok(utf8.len()) + } else { + // Make sure we didn't end up writing only half of a surrogate pair (even though + // the chance is tiny). Because it is not possible for user code to re-slice + // `data` in such a way that a missing surrogate can be produced (and also + // because of the UTF-8 validation above), write the missing surrogate out + // now. Buffering it would mean we have to lie about the number of bytes + // written. + let first_code_unit_remaining = utf16[written]; + if matches!(first_code_unit_remaining, 0xdcee..=0xdfff) { + // low surrogate + // We just hope this works, and give up otherwise + let _ = write_u16s(handle, &utf16[written..written + 1]); + written += 1; + } + // Calculate the number of bytes of `utf8` that were actually written. + let mut count = 0; + for ch in utf16[..written].iter() { + count += match ch { + 0x0000..=0x007f => 1, + 0x0080..=0x07ff => 2, + 0xdcee..=0xdfff => 1, // Low surrogate. We already counted 3 bytes for the other. + _ => 3, + }; + } + debug_assert!(String::from_utf16(&utf16[..written]).unwrap() == utf8[..count]); + Ok(count) + } +} + +fn write_u16s(handle: RawHandle, data: &[u16]) -> std::io::Result { + debug_assert!(data.len() < u32::MAX as usize); + let mut written = 0; + let result = unsafe { + WriteConsoleW(handle, data.as_ptr(), data.len() as u32, &mut written, std::ptr::null_mut()) + }; + if result == 0 { Err(std::io::Error::last_os_error()) } else { Ok(written as usize) } +} diff --git a/yazi-shared/src/utf8.rs b/yazi-shared/src/utf8.rs new file mode 100644 index 00000000..6bf021d7 --- /dev/null +++ b/yazi-shared/src/utf8.rs @@ -0,0 +1,53 @@ +// https://tools.ietf.org/html/rfc3629 +const UTF8_CHAR_WIDTH: &[u8; 256] = &[ + // 1 2 3 4 5 6 7 8 9 A B C D E F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F +]; + +/// Given a first byte, determines how many bytes are in this UTF-8 character. +#[must_use] +#[inline] +pub const fn utf8_char_width(b: u8) -> usize { UTF8_CHAR_WIDTH[b as usize] as usize } + +/// Finds the closest `x` not exceeding `index` where [`is_char_boundary(x)`] is +/// `true`. +/// +/// This method can help you truncate a string so that it's still valid UTF-8, +/// but doesn't exceed a given number of bytes. Note that this is done purely at +/// the character level and can still visually split graphemes, even though the +/// underlying characters aren't split. For example, the emoji 🧑‍🔬 (scientist) +/// could be split so that the string only includes 🧑 (person) instead. +#[inline] +pub fn floor_char_boundary(s: &str, index: usize) -> usize { + if index >= s.len() { + s.len() + } else { + let lower_bound = index.saturating_sub(3); + let new_index = + s.as_bytes()[lower_bound..=index].iter().rposition(|&b| is_utf8_char_boundary(b)); + + // SAFETY: we know that the character boundary will be within four bytes + unsafe { lower_bound + new_index.unwrap_unchecked() } + } +} + +#[inline] +const fn is_utf8_char_boundary(b: u8) -> bool { + // This is bit magic equivalent to: b < 128 || b >= 192 + (b as i8) >= -0x40 +} From 76adb97e22bebfffe609e74e962c858ce0da695e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Tue, 11 Mar 2025 12:37:31 +0800 Subject: [PATCH 08/11] ci: add label only if not removed manually (#2470) --- scripts/validate-form/main.js | 49 +++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/scripts/validate-form/main.js b/scripts/validate-form/main.js index 2b71368a..ecbfff02 100644 --- a/scripts/validate-form/main.js +++ b/scripts/validate-form/main.js @@ -61,10 +61,49 @@ module.exports = async ({ github, context, core }) => { } } + async function lastLabeledAt(id) { + try { + const { data: events } = await github.rest.issues.listEvents({ + ...context.repo, + issue_number: id, + per_page : 100, + }) + + const all = events.filter(v => v.event === "labeled" && v.label?.name === LABEL_NAME) + return all.at(-1)?.created_at + } catch (e) { + core.error(`Error getting label timestamp: ${e.message}`) + return null + } + } + + async function removedLabelManually(id) { + try { + const { data: events } = await github.rest.issues.listEvents({ + ...context.repo, + issue_number: id, + per_page : 100, + }) + + const all = events.filter(v => v.event === "unlabeled" && v.label?.name === LABEL_NAME) + return all.length === 0 ? false : !all.at(-1).actor.login.endsWith("[bot]") + } catch (e) { + core.error(`Error checking label removal history: ${e.message}`) + return false + } + } + async function updateLabels(id, mark, body) { try { const marked = await hasLabel(id, LABEL_NAME) - if (mark && !marked) { + + if (!mark && marked) { + await github.rest.issues.removeLabel({ + ...context.repo, + issue_number: id, + name : LABEL_NAME, + }) + } else if (mark && !marked && !await removedLabelManually(id)) { await github.rest.issues.addLabels({ ...context.repo, issue_number: id, @@ -75,12 +114,6 @@ module.exports = async ({ github, context, core }) => { issue_number: id, body, }) - } else if (!mark && marked) { - await github.rest.issues.removeLabel({ - ...context.repo, - issue_number: id, - name : LABEL_NAME, - }) } } catch (e) { core.error(`Error updating labels: ${e.message}`) @@ -99,7 +132,7 @@ module.exports = async ({ github, context, core }) => { const twoDaysAgo = new Date(now - 2 * 24 * 60 * 60 * 1000) for (const issue of issues) { - const markedAt = new Date(issue.labels_at || issue.created_at) + const markedAt = new Date(await lastLabeledAt(issue.number) || issue.created_at) if (markedAt < twoDaysAgo) { await github.rest.issues.update({ ...context.repo, From a0c6842626fec1d6e0d29f1f733f27d91277579e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Tue, 11 Mar 2025 12:57:47 +0800 Subject: [PATCH 09/11] fix: respect hidden status of directory junctions and symlinks themselves on Windows (#2471) --- Cargo.lock | 20 +++++----- Cargo.toml | 4 +- yazi-config/src/theme/is.rs | 2 +- yazi-core/src/mgr/watcher.rs | 2 +- yazi-core/src/tab/commands/search.rs | 2 +- yazi-core/src/tab/folder.rs | 2 +- yazi-core/src/tab/preview.rs | 2 +- yazi-fs/src/{ => cha}/cha.rs | 58 +++++++--------------------- yazi-fs/src/cha/kind.rs | 44 +++++++++++++++++++++ yazi-fs/src/cha/mod.rs | 1 + yazi-fs/src/file.rs | 2 +- yazi-fs/src/files.rs | 2 +- yazi-fs/src/fns.rs | 2 +- yazi-fs/src/lib.rs | 4 +- yazi-fs/src/mounts/partitions.rs | 2 +- yazi-fs/src/op.rs | 3 +- yazi-plugin/src/bindings/cha.rs | 10 ++--- yazi-plugin/src/utils/preview.rs | 2 +- yazi-plugin/src/utils/spot.rs | 2 +- yazi-scheduler/src/file/file.rs | 2 +- yazi-scheduler/src/file/op.rs | 2 +- 21 files changed, 93 insertions(+), 77 deletions(-) rename yazi-fs/src/{ => cha}/cha.rs (79%) create mode 100644 yazi-fs/src/cha/kind.rs create mode 100644 yazi-fs/src/cha/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 61caa028..6f32328e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,9 +367,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -418,9 +418,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -1231,9 +1231,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown", @@ -1767,9 +1767,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "onig" diff --git a/Cargo.toml b/Cargo.toml index 357b80be..5205919e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,14 +18,14 @@ ansi-to-tui = "7.0.0" anyhow = "1.0.97" base64 = "0.22.1" bitflags = "2.9.0" -clap = { version = "4.5.31", features = [ "derive" ] } +clap = { version = "4.5.32", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.28.1", features = [ "event-stream" ] } dirs = "6.0.0" foldhash = "0.1.4" futures = "0.3.31" globset = "0.4.16" -indexmap = { version = "2.7.1", features = [ "serde" ] } +indexmap = { version = "2.8.0", features = [ "serde" ] } libc = "0.2.170" lru = "0.13.0" md-5 = "0.10.6" diff --git a/yazi-config/src/theme/is.rs b/yazi-config/src/theme/is.rs index 61f18b9b..9af86250 100644 --- a/yazi-config/src/theme/is.rs +++ b/yazi-config/src/theme/is.rs @@ -1,5 +1,5 @@ use serde::Deserialize; -use yazi_fs::Cha; +use yazi_fs::cha::Cha; #[derive(Default, Deserialize)] #[serde(rename_all = "kebab-case")] diff --git a/yazi-core/src/mgr/watcher.rs b/yazi-core/src/mgr/watcher.rs index 6afc46d9..757ed98f 100644 --- a/yazi-core/src/mgr/watcher.rs +++ b/yazi-core/src/mgr/watcher.rs @@ -6,7 +6,7 @@ use parking_lot::RwLock; use tokio::{fs, pin, sync::{mpsc::{self, UnboundedReceiver}, watch}}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tracing::error; -use yazi_fs::{Cha, File, Files, FilesOp, realname_unchecked}; +use yazi_fs::{File, Files, FilesOp, cha::Cha, realname_unchecked}; use yazi_proxy::WATCHER; use yazi_shared::{RoCell, url::Url}; diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index 907227c2..09ac84fd 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -5,7 +5,7 @@ use tokio::pin; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tracing::error; use yazi_config::popup::InputCfg; -use yazi_fs::{Cha, FilesOp}; +use yazi_fs::{FilesOp, cha::Cha}; use yazi_plugin::external; use yazi_proxy::{AppProxy, InputProxy, MgrProxy, TabProxy, options::{SearchOpt, SearchOptVia}}; diff --git a/yazi-core/src/tab/folder.rs b/yazi-core/src/tab/folder.rs index b7aa287a..459e5b8c 100644 --- a/yazi-core/src/tab/folder.rs +++ b/yazi-core/src/tab/folder.rs @@ -2,7 +2,7 @@ use std::mem; use yazi_config::{LAYOUT, MGR}; use yazi_dds::Pubsub; -use yazi_fs::{Cha, File, Files, FilesOp, FolderStage, Step}; +use yazi_fs::{File, Files, FilesOp, FolderStage, Step, cha::Cha}; use yazi_proxy::MgrProxy; use yazi_shared::{Id, url::{Url, Urn, UrnBuf}}; diff --git a/yazi-core/src/tab/preview.rs b/yazi-core/src/tab/preview.rs index a55ac3c8..65eccd6a 100644 --- a/yazi-core/src/tab/preview.rs +++ b/yazi-core/src/tab/preview.rs @@ -5,7 +5,7 @@ use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_util::sync::CancellationToken; use yazi_adapter::ADAPTOR; use yazi_config::PLUGIN; -use yazi_fs::{Cha, File, Files, FilesOp}; +use yazi_fs::{File, Files, FilesOp, cha::Cha}; use yazi_macro::render; use yazi_plugin::{external::Highlighter, isolate, utils::PreviewLock}; use yazi_shared::{MIME_DIR, url::Url}; diff --git a/yazi-fs/src/cha.rs b/yazi-fs/src/cha/cha.rs similarity index 79% rename from yazi-fs/src/cha.rs rename to yazi-fs/src/cha/cha.rs index 9ac97980..37732a78 100644 --- a/yazi-fs/src/cha.rs +++ b/yazi-fs/src/cha/cha.rs @@ -1,22 +1,8 @@ use std::{fs::{FileType, Metadata}, path::Path, time::SystemTime}; -use bitflags::bitflags; use yazi_macro::{unix_either, win_either}; -bitflags! { - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] - pub struct ChaKind: u8 { - const DIR = 0b00000001; - - const HIDDEN = 0b00000010; - const LINK = 0b00000100; - const ORPHAN = 0b00001000; - - const DUMMY = 0b00010000; - #[cfg(windows)] - const SYSTEM = 0b00100000; - } -} +use super::ChaKind; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Cha { @@ -39,8 +25,8 @@ pub struct Cha { pub nlink: libc::nlink_t, } -impl From for Cha { - fn from(m: Metadata) -> Self { +impl From<&Metadata> for Cha { + fn from(m: &Metadata) -> Self { let mut kind = ChaKind::empty(); if m.is_dir() { kind |= ChaKind::DIR; @@ -89,6 +75,10 @@ impl From for Cha { } } +impl From for Cha { + fn from(m: Metadata) -> Self { Self::from(&m) } +} + impl From for Cha { fn from(t: FileType) -> Self { let mut kind = ChaKind::DUMMY; @@ -134,9 +124,8 @@ impl From for Cha { } impl Cha { - #[inline] pub async fn new(path: &Path, mut meta: Metadata) -> Self { - let mut attached = ChaKind::empty(); + let mut attached = ChaKind::hidden(path, &meta); if meta.is_symlink() { attached |= ChaKind::LINK; @@ -146,35 +135,16 @@ impl Cha { attached |= ChaKind::ORPHAN; } - let mut cha = Self::new_nofollow(path, meta); - cha.kind |= attached; - cha + let mut me = Self::from(meta); + me.kind |= attached; + me } #[inline] pub fn new_nofollow(_path: &Path, meta: Metadata) -> Self { - let mut attached = ChaKind::empty(); - - #[cfg(unix)] - if yazi_shared::url::Urn::new(_path).is_hidden() { - attached |= ChaKind::HIDDEN; - } - #[cfg(windows)] - { - use std::os::windows::fs::MetadataExt; - - use windows_sys::Win32::Storage::FileSystem::{FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_SYSTEM}; - if meta.file_attributes() & FILE_ATTRIBUTE_HIDDEN != 0 { - attached |= ChaKind::HIDDEN; - } - if meta.file_attributes() & FILE_ATTRIBUTE_SYSTEM != 0 { - attached |= ChaKind::SYSTEM; - } - } - - let mut cha = Self::from(meta); - cha.kind |= attached; - cha + let mut me = Self::from(&meta); + me.kind |= ChaKind::hidden(_path, &meta); + me } #[inline] diff --git a/yazi-fs/src/cha/kind.rs b/yazi-fs/src/cha/kind.rs new file mode 100644 index 00000000..8b71aa41 --- /dev/null +++ b/yazi-fs/src/cha/kind.rs @@ -0,0 +1,44 @@ +use std::{fs::Metadata, path::Path}; + +use bitflags::bitflags; + +bitflags! { + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub struct ChaKind: u8 { + const DIR = 0b00000001; + + const HIDDEN = 0b00000010; + const LINK = 0b00000100; + const ORPHAN = 0b00001000; + + const DUMMY = 0b00010000; + #[cfg(windows)] + const SYSTEM = 0b00100000; + } +} + +impl ChaKind { + #[inline] + pub(super) fn hidden(_path: &Path, _meta: &Metadata) -> Self { + let mut me = Self::empty(); + + #[cfg(unix)] + if yazi_shared::url::Urn::new(_path).is_hidden() { + me |= Self::HIDDEN; + } + #[cfg(windows)] + { + use std::os::windows::fs::MetadataExt; + + use windows_sys::Win32::Storage::FileSystem::{FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_SYSTEM}; + if _meta.file_attributes() & FILE_ATTRIBUTE_HIDDEN != 0 { + me |= Self::HIDDEN; + } + if _meta.file_attributes() & FILE_ATTRIBUTE_SYSTEM != 0 { + me |= Self::SYSTEM; + } + } + + me + } +} diff --git a/yazi-fs/src/cha/mod.rs b/yazi-fs/src/cha/mod.rs new file mode 100644 index 00000000..aa8136b3 --- /dev/null +++ b/yazi-fs/src/cha/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(cha kind); diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index 2c1467ee..18d7ba77 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -4,7 +4,7 @@ use anyhow::Result; use tokio::fs; use yazi_shared::{SyncCell, theme::IconCache, url::{Url, Urn, UrnBuf}}; -use crate::Cha; +use crate::cha::Cha; #[derive(Clone, Debug, Default)] pub struct File { diff --git a/yazi-fs/src/files.rs b/yazi-fs/src/files.rs index bed8bc7b..6d195c61 100644 --- a/yazi-fs/src/files.rs +++ b/yazi-fs/src/files.rs @@ -4,7 +4,7 @@ use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}} use yazi_shared::{Id, url::{Url, Urn, UrnBuf}}; use super::{FilesSorter, Filter}; -use crate::{Cha, FILES_TICKET, File, FilesOp, SortBy, maybe_exists, mounts::PARTITIONS}; +use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, maybe_exists, mounts::PARTITIONS}; #[derive(Default)] pub struct Files { diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index 45206338..cf73a577 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, collections::{HashMap, HashSet, VecDeque}, ffi::{OsStr, O use anyhow::{Result, bail}; use tokio::{fs, io::{self, AsyncWriteExt}, select, sync::{mpsc, oneshot}, time}; -use super::Cha; +use crate::cha::Cha; #[inline] pub async fn must_exists(p: impl AsRef) -> bool { fs::symlink_metadata(p).await.is_ok() } diff --git a/yazi-fs/src/lib.rs b/yazi-fs/src/lib.rs index e028bc45..22488e75 100644 --- a/yazi-fs/src/lib.rs +++ b/yazi-fs/src/lib.rs @@ -1,8 +1,8 @@ #![allow(clippy::if_same_then_else, clippy::option_map_unit_fn)] -yazi_macro::mod_pub!(mounts); +yazi_macro::mod_pub!(cha mounts); -yazi_macro::mod_flat!(cha cwd file files filter fns op path sorter sorting stage step xdg); +yazi_macro::mod_flat!(cwd file files filter fns op path sorter sorting stage step xdg); pub fn init() { CWD.init(<_>::default()); diff --git a/yazi-fs/src/mounts/partitions.rs b/yazi-fs/src/mounts/partitions.rs index ef589eec..eacee155 100644 --- a/yazi-fs/src/mounts/partitions.rs +++ b/yazi-fs/src/mounts/partitions.rs @@ -4,7 +4,7 @@ use parking_lot::RwLock; use yazi_shared::RoCell; use super::Partition; -use crate::Cha; +use crate::cha::Cha; pub(super) type Locked = Arc>; diff --git a/yazi-fs/src/op.rs b/yazi-fs/src/op.rs index d1f4cd70..fe22c95e 100644 --- a/yazi-fs/src/op.rs +++ b/yazi-fs/src/op.rs @@ -2,7 +2,8 @@ use std::collections::{HashMap, HashSet}; use yazi_shared::{Id, Ids, event::Cmd, url::{Url, UrnBuf}}; -use super::{Cha, File}; +use super::File; +use crate::cha::Cha; pub static FILES_TICKET: Ids = Ids::new(); diff --git a/yazi-plugin/src/bindings/cha.rs b/yazi-plugin/src/bindings/cha.rs index 4329be9c..dca7ef50 100644 --- a/yazi-plugin/src/bindings/cha.rs +++ b/yazi-plugin/src/bindings/cha.rs @@ -1,18 +1,18 @@ use std::{ops::Deref, time::{Duration, SystemTime, UNIX_EPOCH}}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, UserDataMethods}; -use yazi_fs::ChaKind; +use yazi_fs::cha::ChaKind; #[derive(Clone, Copy, FromLua)] -pub struct Cha(yazi_fs::Cha); +pub struct Cha(yazi_fs::cha::Cha); impl Deref for Cha { - type Target = yazi_fs::Cha; + type Target = yazi_fs::cha::Cha; fn deref(&self) -> &Self::Target { &self.0 } } -impl> From for Cha { +impl> From for Cha { fn from(cha: T) -> Self { Self(cha.into()) } } @@ -33,7 +33,7 @@ impl Cha { let kind = ChaKind::from_bits(t.raw_get("kind")?).ok_or_else(|| "Invalid kind".into_lua_err())?; - Self::from(yazi_fs::Cha { + Self::from(yazi_fs::cha::Cha { kind, len: t.raw_get("len").unwrap_or_default(), atime: parse_time(t.raw_get("atime").ok())?, diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 19915519..175d94d7 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -9,7 +9,7 @@ use crate::{elements::{Area, Rect, Renderable, Text, WRAP, WRAP_NO}, external::H #[derive(Debug, Default)] pub struct PreviewLock { pub url: yazi_shared::url::Url, - pub cha: yazi_fs::Cha, + pub cha: yazi_fs::cha::Cha, pub mime: String, pub skip: usize, diff --git a/yazi-plugin/src/utils/spot.rs b/yazi-plugin/src/utils/spot.rs index 70e39368..26a43e90 100644 --- a/yazi-plugin/src/utils/spot.rs +++ b/yazi-plugin/src/utils/spot.rs @@ -8,7 +8,7 @@ use crate::{elements::Renderable, file::FileRef}; pub struct SpotLock { pub url: yazi_shared::url::Url, - pub cha: yazi_fs::Cha, + pub cha: yazi_fs::cha::Cha, pub mime: String, pub skip: usize, diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 16b7ed0f..329ab4ec 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -4,7 +4,7 @@ use anyhow::{Result, anyhow}; use tokio::{fs::{self, DirEntry}, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; use yazi_config::TASKS; -use yazi_fs::{Cha, calculate_size, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, skip_path}; +use yazi_fs::{calculate_size, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, skip_path}; use yazi_shared::url::Url; use super::{FileOp, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}; diff --git a/yazi-scheduler/src/file/op.rs b/yazi-scheduler/src/file/op.rs index dd0c474c..0d87fde8 100644 --- a/yazi-scheduler/src/file/op.rs +++ b/yazi-scheduler/src/file/op.rs @@ -1,4 +1,4 @@ -use yazi_fs::Cha; +use yazi_fs::cha::Cha; use yazi_shared::url::Url; #[derive(Debug)] From faee35fc44bd6cfe924410f1a3dc3b8b8eb12fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Tue, 11 Mar 2025 15:19:30 +0800 Subject: [PATCH 10/11] fix: force ANSI for keyboard progressive enhancement on Windows (#2474) --- yazi-fm/src/term.rs | 18 ++++++++---------- yazi-shared/src/tty/tty.rs | 8 +++++++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs index c73a3441..78a63602 100644 --- a/yazi-fm/src/term.rs +++ b/yazi-fm/src/term.rs @@ -1,7 +1,7 @@ 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}}; +use crossterm::{Command, 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}; @@ -59,13 +59,11 @@ impl Term { ); if CSI_U.load(Ordering::Relaxed) { - queue!( - TTY.writer(), - PushKeyboardEnhancementFlags( - KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES - | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS - ) - )?; + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS, + ) + .write_ansi(&mut TTY.writer())?; } term.hide_cursor()?; @@ -76,7 +74,7 @@ impl Term { fn stop(&mut self) -> Result<()> { if CSI_U.swap(false, Ordering::Relaxed) { - execute!(TTY.writer(), PopKeyboardEnhancementFlags)?; + PopKeyboardEnhancementFlags.write_ansi(&mut TTY.writer())?; } execute!( @@ -93,7 +91,7 @@ impl Term { pub(super) fn goodbye(f: impl FnOnce() -> bool) -> ! { if CSI_U.swap(false, Ordering::Relaxed) { - execute!(TTY.writer(), PopKeyboardEnhancementFlags).ok(); + PopKeyboardEnhancementFlags.write_ansi(&mut TTY.writer()).ok(); } if !MGR.title_format.is_empty() { diff --git a/yazi-shared/src/tty/tty.rs b/yazi-shared/src/tty/tty.rs index 74690348..3da833ec 100644 --- a/yazi-shared/src/tty/tty.rs +++ b/yazi-shared/src/tty/tty.rs @@ -67,8 +67,14 @@ impl Read for TtyReader<'_> { // --- Writer pub struct TtyWriter<'a>(&'a Mutex>); -impl Write for TtyWriter<'_> { +impl std::io::Write for TtyWriter<'_> { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0.lock().write(buf) } fn flush(&mut self) -> std::io::Result<()> { self.0.lock().flush() } } + +impl std::fmt::Write for TtyWriter<'_> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.0.lock().write_all(s.as_bytes()).map_err(|_| std::fmt::Error) + } +} From abfbd1cd9f0dd428d134f257ec89c25dac842b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Wed, 12 Mar 2025 14:17:07 +0800 Subject: [PATCH 11/11] feat: new `fs.expand_url` API (#2476) --- Cargo.lock | 1 + yazi-plugin/src/config/runtime.rs | 5 ++- yazi-plugin/src/fs/fs.rs | 12 +++++++ yazi-shared/Cargo.toml | 1 + yazi-shared/src/tty/handle.rs | 53 +++++++++++++++++++++---------- yazi-shared/src/tty/tty.rs | 7 ++-- 6 files changed, 58 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f32328e..37aa8560 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3624,6 +3624,7 @@ dependencies = [ "ratatui", "serde", "tokio", + "tracing", "uzers", "windows-sys 0.59.0", "yazi-macro", diff --git a/yazi-plugin/src/config/runtime.rs b/yazi-plugin/src/config/runtime.rs index 3a770865..164293a3 100644 --- a/yazi-plugin/src/config/runtime.rs +++ b/yazi-plugin/src/config/runtime.rs @@ -31,8 +31,11 @@ impl<'a> Runtime<'a> { fn args(lua: &Lua) -> mlua::Result { Composer::make(lua, 5, |lua, key| { match key { - b"chooser_file" => ARGS.chooser_file.as_ref().map(Url::from).into_lua(lua)?, + b"entries" => { + lua.create_sequence_from(ARGS.entries.iter().map(Url::from))?.into_lua(lua)? + } b"cwd_file" => ARGS.cwd_file.as_ref().map(Url::from).into_lua(lua)?, + b"chooser_file" => ARGS.chooser_file.as_ref().map(Url::from).into_lua(lua)?, _ => return Ok(Value::Nil), } .into_lua(lua) diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 8419514a..347a1547 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -15,6 +15,7 @@ pub fn compose(lua: &Lua) -> mlua::Result { b"create" => create(lua)?, b"remove" => remove(lua)?, b"read_dir" => read_dir(lua)?, + b"expand_url" => expand_url(lua)?, b"unique_name" => unique_name(lua)?, b"partitions" => partitions(lua)?, _ => return Ok(Value::Nil), @@ -150,6 +151,17 @@ fn read_dir(lua: &Lua) -> mlua::Result { }) } +fn expand_url(lua: &Lua) -> mlua::Result { + lua.create_function(|_, value: Value| { + use yazi_fs::expand_path; + Ok(Url::from(match value { + Value::String(s) => expand_path(s.to_str()?.as_ref()), + Value::UserData(ud) => expand_path(&*ud.borrow::()?), + _ => Err("must be a string or a Url".into_lua_err())?, + })) + }) +} + fn unique_name(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, url: UrlRef| async move { match yazi_fs::unique_name(url.clone(), async { false }).await { diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 8c80fa90..0afc9b26 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -22,6 +22,7 @@ percent-encoding = "2.3.1" ratatui = { workspace = true } serde = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } [target."cfg(unix)".dependencies] libc = { workspace = true } diff --git a/yazi-shared/src/tty/handle.rs b/yazi-shared/src/tty/handle.rs index 7a54936f..4a4effce 100644 --- a/yazi-shared/src/tty/handle.rs +++ b/yazi-shared/src/tty/handle.rs @@ -1,5 +1,7 @@ use std::{io::{Error, ErrorKind, Read, Write}, time::Duration}; +use tracing::error; + pub struct Handle { #[cfg(unix)] inner: std::os::fd::RawFd, @@ -32,7 +34,7 @@ impl Read for Handle { 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(); + _ = f.into_raw_fd(); result } #[cfg(windows)] @@ -40,7 +42,7 @@ impl Read for Handle { 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(); + _ = f.into_raw_handle(); result } } @@ -75,18 +77,23 @@ impl Write for Handle { #[cfg(unix)] impl Handle { - pub(super) fn new(out: bool) -> std::io::Result { + pub(super) fn new(out: bool) -> 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, + use libc::{STDIN_FILENO, STDOUT_FILENO}; + + let resort = Self { inner: if out { STDOUT_FILENO } else { STDIN_FILENO }, close: false }; + if unsafe { libc::isatty(resort.inner) } == 1 { + return resort; + } + + match OpenOptions::new().read(!out).write(out).open("/dev/tty") { + Ok(f) => Self { inner: f.into_raw_fd(), close: true }, + Err(err) => { + error!("Failed to open /dev/tty, falling back to stdin/stdout: {err}"); + resort } - }) + } } pub(super) fn poll(&mut self, timeout: Duration) -> std::io::Result { @@ -121,7 +128,9 @@ impl Handle { #[cfg(windows)] impl Handle { - pub(super) fn new(out: bool) -> std::io::Result { + pub(super) fn new(out: bool) -> Self { + use std::{io::{Error, stdin, stdout}, os::windows::io::AsRawHandle}; + use windows_sys::Win32::{Foundation::{GENERIC_READ, GENERIC_WRITE, INVALID_HANDLE_VALUE}, Globalization::CP_UTF8, Storage::FileSystem::{CreateFileW, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING}, System::Console::GetConsoleOutputCP}; let name: Vec = if out { "CONOUT$\0" } else { "CONIN$\0" }.encode_utf16().collect(); @@ -137,15 +146,25 @@ impl Handle { ) }; - if result == INVALID_HANDLE_VALUE { - Err(std::io::Error::last_os_error()) - } else { - Ok(Self { + if result != INVALID_HANDLE_VALUE { + return Self { inner: result, close: true, out_utf8: unsafe { GetConsoleOutputCP() } == CP_UTF8, incomplete_utf8: Default::default(), - }) + }; + } + + error!( + "Failed to open {}, falling back to stdin/stdout: {}", + if out { "CONOUT$" } else { "CONIN$" }, + Error::last_os_error() + ); + Self { + inner: if out { stdout().as_raw_handle() } else { stdin().as_raw_handle() }, + close: false, + out_utf8: unsafe { GetConsoleOutputCP() } == CP_UTF8, + incomplete_utf8: Default::default(), } } diff --git a/yazi-shared/src/tty/tty.rs b/yazi-shared/src/tty/tty.rs index 3da833ec..cbf4d8f3 100644 --- a/yazi-shared/src/tty/tty.rs +++ b/yazi-shared/src/tty/tty.rs @@ -11,9 +11,10 @@ pub struct Tty { 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)) } + Self { + stdin: Mutex::new(Handle::new(false)), + stdout: Mutex::new(BufWriter::new(Handle::new(true))), + } } }