refactor: move several UI-related APIs from the ya namespace to ui (#2939)

This commit is contained in:
三咲雅 misaki masa 2025-07-02 23:26:13 +08:00 committed by sxyazi
parent 4ff7dae721
commit ffdd74b6ab
No known key found for this signature in database
15 changed files with 205 additions and 96 deletions

View file

@ -150,7 +150,7 @@ jobs:
fetch-depth: 0
- name: Setup LXD
uses: canonical/setup-lxd@v0.1.2
uses: canonical/setup-lxd@v0.1.3
- name: Setup snapcraft
run: sudo snap install --classic snapcraft

17
Cargo.lock generated
View file

@ -1224,6 +1224,17 @@ dependencies = [
"syn",
]
[[package]]
name = "io-uring"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"libc",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@ -2666,17 +2677,19 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.45.1"
version = "1.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",

View file

@ -45,7 +45,7 @@ scopeguard = "1.2.0"
serde = { version = "1.0.219", features = [ "derive" ] }
serde_json = "1.0.140"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.45.1", features = [ "full" ] }
tokio = { version = "1.46.0", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.15"
toml = { version = "0.8.23" }

View file

@ -28,7 +28,7 @@ function Header:cwd()
end
local s = ya.readable_path(tostring(self._current.cwd)) .. self:flags()
return ui.Span(ya.truncate(s, { max = max, rtl = true })):style(th.mgr.cwd)
return ui.Span(ui.truncate(s, { max = max, rtl = true })):style(th.mgr.cwd)
end
function Header:flags()

View file

@ -23,7 +23,7 @@ function Tabs:redraw()
local pos = lines[1]:width()
local max = math.floor(self:inner_width() / #cx.tabs)
for i = 1, #cx.tabs do
local name = ya.truncate(string.format(" %d %s ", i, cx.tabs[i].name), { max = max })
local name = ui.truncate(string.format(" %d %s ", i, cx.tabs[i].name), { max = max })
if i == cx.tabs.idx then
lines[#lines + 1] = ui.Line {
ui.Span(th.tabs.sep_inner.open):style(th.tabs.inactive),

View file

@ -33,7 +33,7 @@ function M:peek(job)
left[#left] = ui.Line {
left[#left],
ya.truncate(f.path, {
ui.truncate(f.path, {
rtl = true,
max = math.max(0, job.area.w - ui.width(left[#left]) - ui.width(right[#right])),
}),

View file

@ -11,7 +11,7 @@ end)
function M:entry()
ya.emit("escape", { visual = true })
local _permit = ya.hide()
local _permit = ui.hide()
local cwd, selected = state()
local output, err = M.run_with(cwd, selected)

View file

@ -87,7 +87,7 @@ local function entry()
return fail("No directory history found, check Zoxide's doc to set it up and restart Yazi.")
end
local _permit = ya.hide()
local _permit = ui.hide()
local child, err1 = Command("zoxide")
:arg({ "query", "-i", "--exclude", st.cwd })
:env("SHELL", "sh")

View file

@ -28,8 +28,11 @@ pub fn compose(lua: &Lua) -> mlua::Result<Value> {
b"Wrap" => super::Wrap::compose(lua)?,
b"area" => super::Utils::area(lua)?,
b"hide" => super::Utils::hide(lua)?,
b"width" => super::Utils::width(lua)?,
b"redraw" => super::Utils::redraw(lua)?,
b"render" => super::Utils::render(lua)?,
b"truncate" => super::Utils::truncate(lua)?,
_ => return Ok(Value::Nil),
}
.into_lua(lua)

View file

@ -1,9 +1,12 @@
use mlua::{ExternalError, IntoLua, Lua, ObjectLike, Table, Value};
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, ObjectLike, Table, Value};
use tracing::error;
use unicode_width::UnicodeWidthStr;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use yazi_config::LAYOUT;
use yazi_macro::render;
use yazi_proxy::{AppProxy, HIDER};
use super::{Line, Rect, Span};
use crate::bindings::{Permit, PermitRef};
pub(super) struct Utils;
@ -22,6 +25,22 @@ impl Utils {
f.into_lua(lua)
}
pub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_async_function(|lua, ()| async move {
if lua.named_registry_value::<PermitRef<fn()>>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
return Err("Cannot hide while already hidden".into_lua_err());
}
let permit = HIDER.acquire().await.unwrap();
AppProxy::stop().await;
lua.set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume as fn()))?;
lua.named_registry_value::<AnyUserData>("HIDE_PERMIT")
})?;
f.into_lua(lua)
}
pub(super) fn width(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|_, v: Value| match v {
Value::String(s) => {
@ -77,4 +96,149 @@ impl Utils {
f.into_lua(lua)
}
pub(super) fn render(lua: &Lua) -> mlua::Result<Value> {
let f = lua.create_function(|_, ()| {
render!();
Ok(())
})?;
f.into_lua(lua)
}
pub(super) fn truncate(lua: &Lua) -> mlua::Result<Value> {
fn traverse(
it: impl Iterator<Item = (usize, char)>,
max: usize,
) -> (Option<usize>, usize, bool) {
let (mut adv, mut last) = (0, 0);
let idx = it
.take_while(|&(_, c)| {
(last, adv) = (adv, adv + c.width().unwrap_or(0));
adv <= max
})
.map(|(i, _)| i)
.last();
(idx, last, adv > max)
}
let f = lua.create_function(|lua, (s, t): (mlua::String, Table)| {
let b = s.as_bytes();
if b.is_empty() {
return Ok(s);
}
let max = t.raw_get("max")?;
if b.len() <= max {
return Ok(s);
} else if max < 1 {
return lua.create_string("");
}
let lossy = String::from_utf8_lossy(&b);
let rtl = t.raw_get("rtl").unwrap_or(false);
let (idx, width, remain) = if rtl {
traverse(lossy.char_indices().rev(), max)
} else {
traverse(lossy.char_indices(), max)
};
let Some(idx) = idx else { return lua.create_string("") };
if !remain {
return Ok(s);
}
let result: Vec<_> = match (rtl, width == max) {
(false, false) => {
let len = lossy[idx..].chars().next().map_or(0, |c| c.len_utf8());
lossy[..idx + len].bytes().chain("".bytes()).collect()
}
(false, true) => lossy[..idx].bytes().chain("".bytes()).collect(),
(true, false) => "".bytes().chain(lossy[idx..].bytes()).collect(),
(true, true) => {
let len = lossy[idx..].chars().next().map_or(0, |c| c.len_utf8());
"".bytes().chain(lossy[idx + len..].bytes()).collect()
}
};
lua.create_string(result)
})?;
f.into_lua(lua)
}
}
#[cfg(test)]
mod tests {
use mlua::chunk;
use super::*;
fn truncate(s: &str, max: usize, rtl: bool) -> String {
let lua = Lua::new();
let f = Utils::truncate(&lua).unwrap();
lua
.load(chunk! {
return $f($s, { max = $max, rtl = $rtl })
})
.call(())
.unwrap()
}
#[test]
fn test_truncate() {
assert_eq!(truncate("你好world", 0, false), "");
assert_eq!(truncate("你好world", 1, false), "");
assert_eq!(truncate("你好world", 2, false), "");
assert_eq!(truncate("你好,世界", 3, false), "你…");
assert_eq!(truncate("你好,世界", 4, false), "你…");
assert_eq!(truncate("你好,世界", 5, false), "你好…");
assert_eq!(truncate("Hello, world", 5, false), "Hell…");
assert_eq!(truncate("Ni好世界", 3, false), "Ni…");
}
#[test]
fn test_truncate_rtl() {
assert_eq!(truncate("world你好", 0, true), "");
assert_eq!(truncate("world你好", 1, true), "");
assert_eq!(truncate("world你好", 2, true), "");
assert_eq!(truncate("你好,世界", 3, true), "…界");
assert_eq!(truncate("你好,世界", 4, true), "…界");
assert_eq!(truncate("你好,世界", 5, true), "…世界");
assert_eq!(truncate("Hello, world", 5, true), "…orld");
assert_eq!(truncate("你好Shi界", 3, true), "…界");
}
#[test]
fn test_truncate_oboe() {
assert_eq!(truncate("Hello, world", 11, false), "Hello, wor…");
assert_eq!(truncate("你好,世界", 9, false), "你好,世…");
assert_eq!(truncate("你好世Jie", 9, false), "你好,世…");
assert_eq!(truncate("Hello, world", 11, true), "…llo, world");
assert_eq!(truncate("你好,世界", 9, true), "…好,世界");
assert_eq!(truncate("Ni好世界", 9, true), "…好,世界");
}
#[test]
fn test_truncate_exact() {
assert_eq!(truncate("Hello, world", 12, false), "Hello, world");
assert_eq!(truncate("你好,世界", 10, false), "你好,世界");
assert_eq!(truncate("Hello, world", 12, true), "Hello, world");
assert_eq!(truncate("你好,世界", 10, true), "你好,世界");
}
#[test]
fn test_truncate_overflow() {
assert_eq!(truncate("Hello, world", 13, false), "Hello, world");
assert_eq!(truncate("你好,世界", 11, false), "你好,世界");
assert_eq!(truncate("Hello, world", 13, true), "Hello, world");
assert_eq!(truncate("你好,世界", 11, true), "你好,世界");
}
}

View file

@ -3,7 +3,7 @@ use yazi_binding::Id;
use yazi_proxy::{AppProxy, HIDER};
use super::Utils;
use crate::bindings::{Permit, PermitRef};
use crate::{bindings::{Permit, PermitRef}, deprecate};
impl Utils {
pub(super) fn id(lua: &Lua) -> mlua::Result<Function> {
@ -18,6 +18,8 @@ impl Utils {
pub(super) fn hide(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, ()| async move {
deprecate!(lua, "`ya.hide()` is deprecated, use `ui.hide()` instead, in your {}\nSee #2939 for more details: https://github.com/sxyazi/yazi/pull/2939");
if lua.named_registry_value::<PermitRef<fn()>>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
return Err("Cannot hide while already hidden".into_lua_err());
}

View file

@ -4,10 +4,13 @@ use yazi_macro::{emit, render};
use yazi_shared::{Layer, event::Cmd};
use super::Utils;
use crate::deprecate;
impl Utils {
pub(super) fn render(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, ()| {
lua.create_function(|lua, ()| {
deprecate!(lua, "`ya.render()` is deprecated, use `ui.render()` instead, in your {}\nSee #2939 for more details: https://github.com/sxyazi/yazi/pull/2939");
render!();
Ok(())
})

View file

@ -44,10 +44,7 @@ impl Utils {
if pos.is_nil() {
pos = t.raw_get("position")?;
if !pos.is_nil() {
deprecate!(
lua,
"The `position` property of `ya.input()` is deprecated, use `pos` instead in your {}\nSee #2921 for more details: https://github.com/sxyazi/yazi/pull/2921"
);
deprecate!(lua, "The `position` property of `ya.input()` is deprecated, use `pos` instead in your {}\nSee #2921 for more details: https://github.com/sxyazi/yazi/pull/2921");
}
}

View file

@ -3,7 +3,7 @@ use twox_hash::XxHash3_128;
use unicode_width::UnicodeWidthChar;
use super::Utils;
use crate::CLIPBOARD;
use crate::{CLIPBOARD, deprecate};
impl Utils {
pub(super) fn hash(lua: &Lua) -> mlua::Result<Function> {
@ -41,6 +41,8 @@ impl Utils {
}
lua.create_function(|lua, (s, t): (mlua::String, Table)| {
deprecate!(lua, "`ya.truncate()` is deprecated, use `ui.truncate()` instead, in your {}\nSee #2939 for more details: https://github.com/sxyazi/yazi/pull/2939");
let b = s.as_bytes();
if b.is_empty() {
return Ok(s);
@ -93,79 +95,3 @@ impl Utils {
})
}
}
#[cfg(test)]
mod tests {
use mlua::chunk;
use super::*;
fn truncate(s: &str, max: usize, rtl: bool) -> String {
let lua = Lua::new();
let f = Utils::truncate(&lua).unwrap();
lua
.load(chunk! {
return $f($s, { max = $max, rtl = $rtl })
})
.call(())
.unwrap()
}
#[test]
fn test_truncate() {
assert_eq!(truncate("你好world", 0, false), "");
assert_eq!(truncate("你好world", 1, false), "");
assert_eq!(truncate("你好world", 2, false), "");
assert_eq!(truncate("你好,世界", 3, false), "你…");
assert_eq!(truncate("你好,世界", 4, false), "你…");
assert_eq!(truncate("你好,世界", 5, false), "你好…");
assert_eq!(truncate("Hello, world", 5, false), "Hell…");
assert_eq!(truncate("Ni好世界", 3, false), "Ni…");
}
#[test]
fn test_truncate_rtl() {
assert_eq!(truncate("world你好", 0, true), "");
assert_eq!(truncate("world你好", 1, true), "");
assert_eq!(truncate("world你好", 2, true), "");
assert_eq!(truncate("你好,世界", 3, true), "…界");
assert_eq!(truncate("你好,世界", 4, true), "…界");
assert_eq!(truncate("你好,世界", 5, true), "…世界");
assert_eq!(truncate("Hello, world", 5, true), "…orld");
assert_eq!(truncate("你好Shi界", 3, true), "…界");
}
#[test]
fn test_truncate_oboe() {
assert_eq!(truncate("Hello, world", 11, false), "Hello, wor…");
assert_eq!(truncate("你好,世界", 9, false), "你好,世…");
assert_eq!(truncate("你好世Jie", 9, false), "你好,世…");
assert_eq!(truncate("Hello, world", 11, true), "…llo, world");
assert_eq!(truncate("你好,世界", 9, true), "…好,世界");
assert_eq!(truncate("Ni好世界", 9, true), "…好,世界");
}
#[test]
fn test_truncate_exact() {
assert_eq!(truncate("Hello, world", 12, false), "Hello, world");
assert_eq!(truncate("你好,世界", 10, false), "你好,世界");
assert_eq!(truncate("Hello, world", 12, true), "Hello, world");
assert_eq!(truncate("你好,世界", 10, true), "你好,世界");
}
#[test]
fn test_truncate_overflow() {
assert_eq!(truncate("Hello, world", 13, false), "Hello, world");
assert_eq!(truncate("你好,世界", 11, false), "你好,世界");
assert_eq!(truncate("Hello, world", 13, true), "Hello, world");
assert_eq!(truncate("你好,世界", 11, true), "你好,世界");
}
}

View file

@ -30,8 +30,9 @@ impl Input {
return;
}
let (o_cur, n_cur) = (snap.cursor, opt.step.cursor(snap));
render!(self.handle_op(n_cur, false));
let o_cur = snap.cursor;
render!(self.handle_op(opt.step.cursor(snap), false));
let n_cur = self.snap().cursor;
let (limit, snap) = (self.limit, self.snap_mut());
if snap.value.is_empty() {