This commit is contained in:
sxyazi 2024-04-15 15:28:51 +08:00
parent 224495fd8b
commit 68c1455d08
No known key found for this signature in database
18 changed files with 272 additions and 115 deletions

11
Cargo.lock generated
View file

@ -2712,13 +2712,22 @@ dependencies = [
"clap_complete_fig",
"clap_complete_nushell",
"serde",
"uzers",
"vergen",
"yazi-adaptor",
"yazi-config",
"yazi-shared",
]
[[package]]
name = "yazi-cli"
version = "0.2.4"
dependencies = [
"anyhow",
"clap",
"tokio",
"yazi-dds",
]
[[package]]
name = "yazi-config"
version = "0.2.4"

View file

@ -23,6 +23,3 @@ clap_complete = "4.5.2"
clap_complete_nushell = "4.5.1"
clap_complete_fig = "4.5.0"
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
[target."cfg(unix)".dependencies]
uzers = "0.11.3"

View file

@ -9,13 +9,7 @@ pub use boot::*;
pub static ARGS: RoCell<Args> = RoCell::new();
pub static BOOT: RoCell<Boot> = RoCell::new();
#[cfg(unix)]
pub static USERS_CACHE: yazi_shared::RoCell<uzers::UsersCache> = yazi_shared::RoCell::new();
pub fn init() {
ARGS.with(Default::default);
BOOT.with(Default::default);
#[cfg(unix)]
USERS_CACHE.with(Default::default);
}

21
yazi-cli/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "yazi-cli"
version = "0.2.4"
edition = "2021"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi command-line interface"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
# External dependencies
anyhow = "1.0.82"
clap = { version = "4.5.4", features = [ "derive" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
[[bin]]
name = "ya"
path = "src/main.rs"

20
yazi-cli/src/args.rs Normal file
View file

@ -0,0 +1,20 @@
use clap::{command, Parser, Subcommand};
#[derive(Parser)]
#[command(version, about, long_about = None)]
#[command(propagate_version = true)]
pub(super) struct Args {
#[command(subcommand)]
pub(super) command: Command,
}
#[derive(Subcommand)]
pub(super) enum Command {
/// Send a message to remote instances.
Send(CommandSend),
}
#[derive(clap::Args)]
pub(super) struct CommandSend {
pub(super) message: String,
}

21
yazi-cli/src/main.rs Normal file
View file

@ -0,0 +1,21 @@
mod args;
use args::*;
use clap::Parser;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
match &args.command {
Command::Send(cmd) => {
yazi_dds::init();
if let Err(e) = yazi_dds::Client::shot(&cmd.message).await {
eprintln!("Cannot send message: {e}");
std::process::exit(1);
}
}
}
Ok(())
}

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use mlua::{ExternalResult, IntoLua, Lua, Value};
use serde::Serialize;
use super::{BodyBulk, BodyCd, BodyCustom, BodyDelete, BodyHey, BodyHi, BodyHover, BodyMove, BodyRename, BodyTrash, BodyYank};
use super::{BodyBulk, BodyBye, BodyCd, BodyCustom, BodyDelete, BodyHey, BodyHi, BodyHover, BodyMove, BodyRename, BodyTrash, BodyYank};
use crate::Payload;
#[derive(Debug, Serialize)]
@ -10,6 +10,7 @@ use crate::Payload;
pub enum Body<'a> {
Hi(BodyHi<'a>),
Hey(BodyHey),
Bye(BodyBye),
Cd(BodyCd<'a>),
Hover(BodyHover<'a>),
Rename(BodyRename<'a>),
@ -21,28 +22,28 @@ pub enum Body<'a> {
Custom(BodyCustom),
}
impl<'a> Body<'a> {
impl Body<'static> {
pub fn from_str(kind: &str, body: &str) -> Result<Self> {
Ok(match kind {
"hi" => Body::Hi(serde_json::from_str(body)?),
"hey" => Body::Hey(serde_json::from_str(body)?),
"cd" => Body::Cd(serde_json::from_str(body)?),
"hover" => Body::Hover(serde_json::from_str(body)?),
"rename" => Body::Rename(serde_json::from_str(body)?),
"bulk" => Body::Bulk(serde_json::from_str(body)?),
"yank" => Body::Yank(serde_json::from_str(body)?),
"move" => Body::Move(serde_json::from_str(body)?),
"trash" => Body::Trash(serde_json::from_str(body)?),
"delete" => Body::Delete(serde_json::from_str(body)?),
"hi" => Self::Hi(serde_json::from_str(body)?),
"hey" => Self::Hey(serde_json::from_str(body)?),
"bye" => Self::Bye(serde_json::from_str(body)?),
"cd" => Self::Cd(serde_json::from_str(body)?),
"hover" => Self::Hover(serde_json::from_str(body)?),
"rename" => Self::Rename(serde_json::from_str(body)?),
"bulk" => Self::Bulk(serde_json::from_str(body)?),
"yank" => Self::Yank(serde_json::from_str(body)?),
"move" => Self::Move(serde_json::from_str(body)?),
"trash" => Self::Trash(serde_json::from_str(body)?),
"delete" => Self::Delete(serde_json::from_str(body)?),
_ => BodyCustom::from_str(kind, body)?,
})
}
pub fn from_lua(kind: &str, value: Value) -> Result<Self> {
Ok(match kind {
"hi" | "hey" | "cd" | "hover" | "rename" | "bulk" | "yank" | "move" | "trash" | "delete" => {
Err("Cannot construct system event from Lua").into_lua_err()?
}
"hi" | "hey" | "bye" | "cd" | "hover" | "rename" | "bulk" | "yank" | "move" | "trash"
| "delete" => Err("Cannot construct system event").into_lua_err()?,
_ if !kind.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-') => {
Err("Kind must be alphanumeric with dashes").into_lua_err()?
}
@ -50,23 +51,6 @@ impl<'a> Body<'a> {
})
}
#[inline]
pub fn kind(&self) -> &str {
match self {
Self::Hi(_) => "hi",
Self::Hey(_) => "hey",
Self::Cd(_) => "cd",
Self::Hover(_) => "hover",
Self::Rename(_) => "rename",
Self::Bulk(_) => "bulk",
Self::Yank(_) => "yank",
Body::Move(_) => "move",
Body::Trash(_) => "trash",
Body::Delete(_) => "delete",
Self::Custom(b) => b.kind.as_str(),
}
}
pub fn tab(kind: &str, body: &str) -> usize {
match kind {
"cd" | "hover" | "bulk" | "rename" => {}
@ -74,19 +58,42 @@ impl<'a> Body<'a> {
}
match Self::from_str(kind, body) {
Ok(Body::Cd(b)) => b.tab,
Ok(Body::Hover(b)) => b.tab,
Ok(Body::Bulk(b)) => b.tab,
Ok(Body::Rename(b)) => b.tab,
Ok(Self::Cd(b)) => b.tab,
Ok(Self::Hover(b)) => b.tab,
Ok(Self::Bulk(b)) => b.tab,
Ok(Self::Rename(b)) => b.tab,
_ => 0,
}
}
}
impl<'a> Body<'a> {
#[inline]
pub fn kind(&self) -> &str {
match self {
Self::Hi(_) => "hi",
Self::Hey(_) => "hey",
Self::Bye(_) => "bye",
Self::Cd(_) => "cd",
Self::Hover(_) => "hover",
Self::Rename(_) => "rename",
Self::Bulk(_) => "bulk",
Self::Yank(_) => "yank",
Self::Move(_) => "move",
Self::Trash(_) => "trash",
Self::Delete(_) => "delete",
Self::Custom(b) => b.kind.as_str(),
}
}
#[inline]
pub fn with_receiver(self, receiver: u64) -> Payload<'a> {
Payload::new(self).with_receiver(receiver)
}
#[inline]
pub fn with_sender(self, sender: u64) -> Payload<'a> { Payload::new(self).with_sender(sender) }
#[inline]
pub fn with_severity(self, severity: u16) -> Payload<'a> {
Payload::new(self).with_severity(severity)
@ -96,17 +103,18 @@ impl<'a> Body<'a> {
impl IntoLua<'_> for Body<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
match self {
Body::Hi(b) => b.into_lua(lua),
Body::Hey(b) => b.into_lua(lua),
Body::Cd(b) => b.into_lua(lua),
Body::Hover(b) => b.into_lua(lua),
Body::Rename(b) => b.into_lua(lua),
Body::Bulk(b) => b.into_lua(lua),
Body::Yank(b) => b.into_lua(lua),
Body::Move(b) => b.into_lua(lua),
Body::Trash(b) => b.into_lua(lua),
Body::Delete(b) => b.into_lua(lua),
Body::Custom(b) => b.into_lua(lua),
Self::Hi(b) => b.into_lua(lua),
Self::Hey(b) => b.into_lua(lua),
Self::Bye(b) => b.into_lua(lua),
Self::Cd(b) => b.into_lua(lua),
Self::Hover(b) => b.into_lua(lua),
Self::Rename(b) => b.into_lua(lua),
Self::Bulk(b) => b.into_lua(lua),
Self::Yank(b) => b.into_lua(lua),
Self::Move(b) => b.into_lua(lua),
Self::Trash(b) => b.into_lua(lua),
Self::Delete(b) => b.into_lua(lua),
Self::Custom(b) => b.into_lua(lua),
}
}
}

22
yazi-dds/src/body/bye.rs Normal file
View file

@ -0,0 +1,22 @@
use mlua::{ExternalResult, IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyBye {}
impl BodyBye {
#[inline]
pub fn borrowed() -> Body<'static> { Self {}.into() }
}
impl<'a> From<BodyBye> for Body<'a> {
fn from(value: BodyBye) -> Self { Self::Bye(value) }
}
impl IntoLua<'_> for BodyBye {
fn into_lua(self, _: &Lua) -> mlua::Result<Value<'_>> {
Err("BodyBye cannot be converted to Lua").into_lua_err()
}
}

View file

@ -2,6 +2,7 @@
mod body;
mod bulk;
mod bye;
mod cd;
mod custom;
mod delete;
@ -15,6 +16,7 @@ mod yank;
pub use body::*;
pub use bulk::*;
pub use bye::*;
pub use cd::*;
pub use custom::*;
pub use delete::*;

View file

@ -1,15 +1,18 @@
use std::{collections::{HashMap, HashSet}, mem, str::FromStr};
use anyhow::{bail, Result};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};
use yazi_shared::RoCell;
use crate::{body::Body, ClientReader, ClientWriter, Payload, Pubsub, Server};
use crate::{body::{Body, BodyBye, BodyHi}, ClientReader, ClientWriter, Payload, Pubsub, Server, Stream};
pub(super) static ID: RoCell<u64> = RoCell::new();
pub(super) static PEERS: RoCell<RwLock<HashMap<u64, Peer>>> = RoCell::new();
pub(super) static QUEUE: RoCell<mpsc::UnboundedSender<String>> = RoCell::new();
pub(super) static QUEUE_TX: RoCell<mpsc::UnboundedSender<String>> = RoCell::new();
pub(super) static QUEUE_RX: RoCell<mpsc::UnboundedReceiver<String>> = RoCell::new();
#[derive(Debug)]
pub struct Client {
@ -24,7 +27,8 @@ pub struct Peer {
}
impl Client {
pub(super) fn serve(mut rx: mpsc::UnboundedReceiver<String>) {
pub(super) fn serve() {
let mut rx = QUEUE_RX.drop();
while rx.try_recv().is_ok() {}
tokio::spawn(async move {
@ -45,7 +49,9 @@ impl Client {
continue;
};
if line.starts_with("hey,") {
if line.is_empty() {
continue;
} else if line.starts_with("hey,") {
Self::handle_hey(line);
} else {
Payload::from_str(&line).map(|p| p.emit()).ok();
@ -56,17 +62,42 @@ impl Client {
});
}
pub async fn shot(s: &str) -> Result<()> {
let (kind, receiver, sender, body) = Payload::split(s)?;
if receiver != 0 && sender <= u16::MAX as u64 {
bail!("Sender must be greater than 65535 if receiver is non-zero");
}
let payload = format!(
"{}\n{kind},{receiver},{sender},{}\n{}\n",
Payload::new(BodyHi::borrowed(Default::default())),
serde_json::to_string(body)?,
Payload::new(BodyBye::borrowed())
);
let (mut lines, mut writer) = Stream::connect().await?;
writer.write_all(payload.as_bytes()).await?;
writer.flush().await?;
drop(writer);
while let Ok(Some(s)) = lines.next_line().await {
if matches!(Payload::split(&s), Ok((kind, ..)) if kind == "bye") {
break;
}
}
Ok(())
}
#[inline]
pub(super) fn push<'a>(payload: impl Into<Payload<'a>>) {
QUEUE.send(format!("{}\n", payload.into())).ok();
QUEUE_TX.send(format!("{}\n", payload.into())).ok();
}
#[inline]
pub(super) fn able(&self, ability: &str) -> bool { self.abilities.contains(ability) }
async fn connect(server: &mut Option<JoinHandle<()>>) -> (ClientReader, ClientWriter) {
use crate::Stream;
let mut first = true;
loop {
if let Ok(conn) = Stream::connect().await {

View file

@ -16,15 +16,19 @@ pub use pump::*;
pub use sendable::*;
use server::*;
pub use state::*;
pub use stream::*;
use stream::*;
pub fn serve() {
#[cfg(unix)]
pub static USERS_CACHE: yazi_shared::RoCell<uzers::UsersCache> = yazi_shared::RoCell::new();
pub fn init() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
// Client
ID.init(yazi_shared::timestamp_us());
PEERS.with(Default::default);
QUEUE.init(tx);
QUEUE_TX.init(tx);
QUEUE_RX.init(rx);
// Server
CLIENTS.with(Default::default);
@ -34,15 +38,20 @@ pub fn serve() {
LOCAL.with(Default::default);
REMOTE.with(Default::default);
#[cfg(unix)]
USERS_CACHE.with(Default::default);
// Env
std::env::set_var("YAZI_ID", ID.to_string());
std::env::set_var(
"YAZI_LEVEL",
(std::env::var("YAZI_LEVEL").unwrap_or_default().parse().unwrap_or(0u16) + 1).to_string(),
);
}
pub fn serve() {
Pump::serve();
Client::serve(rx);
Client::serve();
}
pub async fn shutdown() { Pump::shutdown().await; }

View file

@ -37,6 +37,11 @@ impl<'a> Payload<'a> {
self
}
pub(super) fn with_sender(mut self, sender: u64) -> Self {
self.sender = sender;
self
}
pub(super) fn with_severity(mut self, severity: u16) -> Self {
self.sender = severity as u64;
self
@ -44,16 +49,7 @@ impl<'a> Payload<'a> {
}
impl Payload<'static> {
pub(super) fn emit(self) {
self.try_flush();
emit!(Call(Cmd::new("accept_payload").with_data(self), Layer::App));
}
}
impl FromStr for Payload<'_> {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
pub fn split(s: &str) -> Result<(&str, u64, u64, &str)> {
let mut parts = s.splitn(4, ',');
let kind = parts.next().ok_or_else(|| anyhow!("empty kind"))?;
@ -66,6 +62,20 @@ impl FromStr for Payload<'_> {
let body = parts.next().ok_or_else(|| anyhow!("empty body"))?;
Ok((kind, receiver, sender, body))
}
pub(super) fn emit(self) {
self.try_flush();
emit!(Call(Cmd::new("accept_payload").with_data(self), Layer::App));
}
}
impl FromStr for Payload<'static> {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (kind, receiver, sender, body) = Self::split(s)?;
Ok(Self { receiver, sender, body: Body::from_str(kind, body)? })
}
}
@ -79,6 +89,7 @@ impl Display for Payload<'_> {
let result = match &self.body {
Body::Hi(b) => serde_json::to_string(b),
Body::Hey(b) => serde_json::to_string(b),
Body::Bye(b) => serde_json::to_string(b),
Body::Cd(b) => serde_json::to_string(b),
Body::Hover(b) => serde_json::to_string(b),
Body::Rename(b) => serde_json::to_string(b),

View file

@ -5,7 +5,7 @@ use parking_lot::RwLock;
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc, task::JoinHandle, time};
use yazi_shared::RoCell;
use crate::{body::{Body, BodyHey}, Client, Payload, Peer, Stream, STATE};
use crate::{body::{Body, BodyBye, BodyHey}, Client, Payload, Peer, Stream, STATE};
pub(super) static CLIENTS: RoCell<RwLock<HashMap<u64, Client>>> = RoCell::new();
@ -42,8 +42,13 @@ impl Server {
continue;
}
let mut parts = line.splitn(4, ',');
let Some(id) = id else { continue };
if line.starts_with("bye,") {
writer.write_all(BodyBye::borrowed().with_receiver(id).with_sender(0).to_string().as_bytes()).await.ok();
break;
}
let mut parts = line.splitn(4, ',');
let Some(kind) = parts.next() else { continue };
let Some(receiver) = parts.next().and_then(|s| s.parse().ok()) else { continue };
let Some(sender) = parts.next().and_then(|s| s.parse::<u64>().ok()) else { continue };

View file

@ -1,41 +1,41 @@
use tokio::io::{BufReader, Lines, ReadHalf, WriteHalf};
pub struct Stream;
pub(super) struct Stream;
use tokio::io::AsyncBufReadExt;
#[cfg(unix)]
pub type ClientReader = Lines<BufReader<ReadHalf<tokio::net::UnixStream>>>;
pub(super) type ClientReader = Lines<BufReader<ReadHalf<tokio::net::UnixStream>>>;
#[cfg(not(unix))]
pub type ClientReader = Lines<BufReader<ReadHalf<tokio::net::TcpStream>>>;
pub(super) type ClientReader = Lines<BufReader<ReadHalf<tokio::net::TcpStream>>>;
#[cfg(unix)]
pub type ClientWriter = WriteHalf<tokio::net::UnixStream>;
pub(super) type ClientWriter = WriteHalf<tokio::net::UnixStream>;
#[cfg(not(unix))]
pub type ClientWriter = WriteHalf<tokio::net::TcpStream>;
pub(super) type ClientWriter = WriteHalf<tokio::net::TcpStream>;
#[cfg(unix)]
pub type ServerListener = tokio::net::UnixListener;
pub(super) type ServerListener = tokio::net::UnixListener;
#[cfg(not(unix))]
pub type ServerListener = tokio::net::TcpListener;
pub(super) type ServerListener = tokio::net::TcpListener;
impl Stream {
#[cfg(unix)]
pub async fn connect() -> std::io::Result<(ClientReader, ClientWriter)> {
pub(super) async fn connect() -> std::io::Result<(ClientReader, ClientWriter)> {
let stream = tokio::net::UnixStream::connect(Self::socket_file()).await?;
let (reader, writer) = tokio::io::split(stream);
Ok((BufReader::new(reader).lines(), writer))
}
#[cfg(not(unix))]
pub async fn connect() -> std::io::Result<(ClientReader, ClientWriter)> {
pub(super) async fn connect() -> std::io::Result<(ClientReader, ClientWriter)> {
let stream = tokio::net::TcpStream::connect("127.0.0.1:33581").await?;
let (reader, writer) = tokio::io::split(stream);
Ok((BufReader::new(reader).lines(), writer))
}
#[cfg(unix)]
pub async fn bind() -> std::io::Result<ServerListener> {
pub(super) async fn bind() -> std::io::Result<ServerListener> {
let p = Self::socket_file();
tokio::fs::remove_file(&p).await.ok();
@ -43,16 +43,17 @@ impl Stream {
}
#[cfg(not(unix))]
pub async fn bind() -> std::io::Result<ServerListener> {
pub(super) async fn bind() -> std::io::Result<ServerListener> {
tokio::net::TcpListener::bind("127.0.0.1:33581").await
}
#[cfg(unix)]
pub(super) fn socket_file() -> std::path::PathBuf {
fn socket_file() -> std::path::PathBuf {
use uzers::Users;
use yazi_boot::USERS_CACHE;
use yazi_shared::Xdg;
use crate::USERS_CACHE;
Xdg::cache_dir().join(format!(".dds-{}.sock", USERS_CACHE.get_current_uid()))
}
}

View file

@ -9,27 +9,27 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
yazi-config = { path = "../yazi-config", version = "0.2.4" }
yazi-core = { path = "../yazi-core", version = "0.2.4" }
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
yazi-config = { path = "../yazi-config", version = "0.2.4" }
yazi-core = { path = "../yazi-core", version = "0.2.4" }
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
# External dependencies
anyhow = "1.0.82"
better-panic = "0.3.0"
crossterm = { version = "0.27.0", features = [ "event-stream" ] }
fdlimit = "0.3.0"
futures = "0.3.30"
mlua = { version = "0.9.7", features = [ "lua54", "vendored" ] }
ratatui = "0.26.1"
scopeguard = "1.2.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
tokio-util = "0.7.10"
anyhow = "1.0.82"
better-panic = "0.3.0"
crossterm = { version = "0.27.0", features = [ "event-stream" ] }
fdlimit = "0.3.0"
futures = "0.3.30"
mlua = { version = "0.9.7", features = [ "lua54", "vendored" ] }
ratatui = "0.26.1"
scopeguard = "1.2.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
tokio-util = "0.7.10"
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -13,7 +13,12 @@ impl App {
};
let kind = payload.body.kind().to_owned();
let map = if payload.receiver == 0 { REMOTE.read() } else { LOCAL.read() };
let map = if payload.receiver == 0 || payload.receiver != payload.sender {
REMOTE.read()
} else {
LOCAL.read()
};
let Some(map) = map.get(&kind).filter(|&m| !m.is_empty()) else {
return;
};

View file

@ -49,11 +49,12 @@ async fn main() -> anyhow::Result<()> {
yazi_proxy::init();
yazi_dds::serve();
yazi_dds::init();
yazi_plugin::init();
yazi_core::init();
yazi_dds::serve();
app::App::serve().await
}

View file

@ -6,7 +6,7 @@ impl Utils {
#[cfg(unix)]
pub(super) fn user(lua: &Lua, ya: &Table) -> mlua::Result<()> {
use uzers::{Groups, Users};
use yazi_boot::USERS_CACHE;
use yazi_dds::USERS_CACHE;
use yazi_shared::hostname;
use crate::utils::HOSTNAME_CACHE;