mirror of
https://github.com/sxyazi/yazi.git
synced 2024-11-28 02:46:33 +03:00
feat: re-implement zoxide
as a built-in plugin (#881)
This commit is contained in:
parent
0650affb76
commit
cd2e7ff945
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
issue-inactive-days: "30"
|
||||
issue-comment: >
|
||||
|
@ -81,7 +81,7 @@ keymap = [
|
||||
{ on = [ "s" ], run = "search fd", desc = "Search files by name using fd" },
|
||||
{ on = [ "S" ], run = "search rg", desc = "Search files by content using ripgrep" },
|
||||
{ on = [ "<C-s>" ], run = "search none", desc = "Cancel the ongoing search" },
|
||||
{ on = [ "z" ], run = "jump zoxide", desc = "Jump to a directory using zoxide" },
|
||||
{ on = [ "z" ], run = "plugin zoxide", desc = "Jump to a directory using zoxide" },
|
||||
{ on = [ "Z" ], run = "jump fzf", desc = "Jump to a directory, or reveal a file using fzf" },
|
||||
|
||||
# Linemode
|
||||
|
@ -1,6 +1,8 @@
|
||||
use yazi_plugin::external::{self, FzfOpt, ZoxideOpt};
|
||||
use yazi_proxy::{AppProxy, TabProxy, HIDER};
|
||||
use yazi_shared::{event::Cmd, fs::ends_with_slash, Defer};
|
||||
use std::time::Duration;
|
||||
|
||||
use yazi_plugin::external::{self, FzfOpt};
|
||||
use yazi_proxy::{options::{NotifyLevel, NotifyOpt}, AppProxy, TabProxy, HIDER};
|
||||
use yazi_shared::{emit, event::Cmd, fs::ends_with_slash, Defer, Layer};
|
||||
|
||||
use crate::tab::Tab;
|
||||
|
||||
@ -34,6 +36,21 @@ impl Tab {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this once Yazi v0.2.7 is released
|
||||
if opt.type_ == OptType::Zoxide {
|
||||
AppProxy::notify(NotifyOpt {
|
||||
title: "Jump".to_owned(),
|
||||
content: r#"The `jump zoxide` command has been deprecated in Yazi v0.2.5. Please replace it with `plugin zoxide` in your `keymap.toml`.
|
||||
|
||||
See https://github.com/sxyazi/yazi/issues/865 for more details."#.to_owned(),
|
||||
level: NotifyLevel::Warn,
|
||||
timeout: Duration::from_secs(15),
|
||||
});
|
||||
|
||||
emit!(Call(Cmd::args("plugin", vec!["zoxide".to_owned()]), Layer::App));
|
||||
return;
|
||||
}
|
||||
|
||||
let cwd = self.current.cwd.clone();
|
||||
tokio::spawn(async move {
|
||||
let _permit = HIDER.acquire().await.unwrap();
|
||||
@ -43,7 +60,7 @@ impl Tab {
|
||||
let result = if opt.type_ == OptType::Fzf {
|
||||
external::fzf(FzfOpt { cwd }).await
|
||||
} else {
|
||||
external::zoxide(ZoxideOpt { cwd }).await
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let Ok(url) = result else {
|
||||
|
@ -16,7 +16,7 @@ impl<'a> Layout<'a> {
|
||||
|
||||
pub(crate) fn available(area: Rect) -> Rect {
|
||||
let chunks =
|
||||
layout::Layout::horizontal([Constraint::Fill(1), Constraint::Length(40), Constraint::Max(1)])
|
||||
layout::Layout::horizontal([Constraint::Fill(1), Constraint::Length(80), Constraint::Max(1)])
|
||||
.split(area);
|
||||
|
||||
let chunks =
|
||||
|
79
yazi-plugin/preset/plugins/zoxide.lua
Normal file
79
yazi-plugin/preset/plugins/zoxide.lua
Normal file
@ -0,0 +1,79 @@
|
||||
local state = ya.sync(function(st)
|
||||
return {
|
||||
cwd = tostring(cx.active.current.cwd),
|
||||
empty = st.empty,
|
||||
}
|
||||
end)
|
||||
|
||||
local set_state = ya.sync(function(st, empty) st.empty = empty end)
|
||||
|
||||
local function notify(s, ...)
|
||||
ya.notify { title = "Zoxide", content = string.format(s, ...), timeout = 5, level = "error" }
|
||||
end
|
||||
|
||||
local function head(cwd)
|
||||
local child = Command("zoxide"):args({ "query", "-l" }):stdout(Command.PIPED):spawn()
|
||||
if not child then
|
||||
return 0
|
||||
end
|
||||
|
||||
local n = 0
|
||||
repeat
|
||||
local next, event = child:read_line()
|
||||
if event ~= 0 then
|
||||
break
|
||||
elseif cwd ~= next:gsub("\n$", "") then
|
||||
n = n + 1
|
||||
end
|
||||
until n >= 2
|
||||
|
||||
child:start_kill()
|
||||
return n
|
||||
end
|
||||
|
||||
local function setup(_, opts)
|
||||
opts = opts or {}
|
||||
|
||||
if opts.update_db then
|
||||
ps.sub(
|
||||
"cd",
|
||||
function()
|
||||
ya.manager_emit("shell", {
|
||||
confirm = true,
|
||||
"zoxide add " .. ya.quote(tostring(cx.active.current.cwd)),
|
||||
})
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function entry()
|
||||
local st = state()
|
||||
if st.empty == true then
|
||||
return notify("No directory history in the database, check out the `zoxide` docs to set it up.")
|
||||
elseif st.empty == nil and head(st.cwd) < 2 then
|
||||
set_state(true)
|
||||
return notify("No directory history in the database, check out the `zoxide` docs to set it up.")
|
||||
end
|
||||
|
||||
local _permit = ya.hide()
|
||||
local child, err = Command("zoxide")
|
||||
:args({ "query", "-i", "--exclude" })
|
||||
:arg(st.cwd)
|
||||
:stdin(Command.INHERIT)
|
||||
:stdout(Command.PIPED)
|
||||
:stderr(Command.INHERIT)
|
||||
:spawn()
|
||||
|
||||
if not child then
|
||||
return notify("Spawn `zoxide` failed with error code %s. Do you have it installed?", err)
|
||||
end
|
||||
|
||||
local output, err = child:wait_with_output()
|
||||
if not output then
|
||||
return notify("`zoxide` exited with error code %s", err)
|
||||
end
|
||||
ya.manager_emit("cd", { output.stdout:gsub("\n$", "") })
|
||||
end
|
||||
|
||||
return { setup = setup, entry = entry }
|
2
yazi-plugin/src/external/mod.rs
vendored
2
yazi-plugin/src/external/mod.rs
vendored
@ -3,11 +3,9 @@ mod fzf;
|
||||
mod highlighter;
|
||||
mod lsar;
|
||||
mod rg;
|
||||
mod zoxide;
|
||||
|
||||
pub use fd::*;
|
||||
pub use fzf::*;
|
||||
pub use highlighter::*;
|
||||
pub use lsar::*;
|
||||
pub use rg::*;
|
||||
pub use zoxide::*;
|
||||
|
26
yazi-plugin/src/external/zoxide.rs
vendored
26
yazi-plugin/src/external/zoxide.rs
vendored
@ -1,26 +0,0 @@
|
||||
use std::process::Stdio;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use tokio::process::Command;
|
||||
use yazi_shared::fs::Url;
|
||||
|
||||
pub struct ZoxideOpt {
|
||||
pub cwd: Url,
|
||||
}
|
||||
|
||||
pub async fn zoxide(opt: ZoxideOpt) -> Result<Url> {
|
||||
let child = Command::new("zoxide")
|
||||
.args(["query", "-i", "--exclude"])
|
||||
.arg(&opt.cwd)
|
||||
.kill_on_drop(true)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let output = child.wait_with_output().await?;
|
||||
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
|
||||
if !selected.is_empty() {
|
||||
return Ok(Url::from(selected));
|
||||
}
|
||||
bail!("No match")
|
||||
}
|
@ -23,6 +23,6 @@ pub use opt::*;
|
||||
pub use runtime::*;
|
||||
|
||||
pub fn init() {
|
||||
crate::init_lua();
|
||||
crate::loader::init();
|
||||
crate::init_lua();
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ impl Loader {
|
||||
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
|
||||
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
|
||||
"video" => include_bytes!("../../preset/plugins/video.lua"),
|
||||
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
|
||||
_ => bail!("plugin not found: {name}"),
|
||||
}))
|
||||
})?;
|
||||
|
@ -1,5 +1,6 @@
|
||||
use mlua::{ExternalResult, Function, IntoLua, Lua, MetaMethod, Table, TableExt, UserData, Value, Variadic};
|
||||
use mlua::{ExternalResult, IntoLua, Lua, MetaMethod, Table, TableExt, UserData, Value, Variadic};
|
||||
|
||||
use super::LOADER;
|
||||
use crate::RtRef;
|
||||
|
||||
pub(super) struct Require;
|
||||
@ -8,15 +9,16 @@ impl Require {
|
||||
pub(super) fn install(lua: &'static Lua) -> mlua::Result<()> {
|
||||
let globals = lua.globals();
|
||||
|
||||
let require = globals.raw_get::<_, Function>("require")?;
|
||||
globals.raw_set(
|
||||
"require",
|
||||
lua.create_function(move |lua, name: mlua::String| {
|
||||
lua.named_registry_value::<RtRef>("rt")?.swap(name.to_str()?);
|
||||
let mod_: Table = require.call(&name)?;
|
||||
lua.create_function(|lua, name: mlua::String| {
|
||||
let s = name.to_str()?;
|
||||
futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?;
|
||||
|
||||
lua.named_registry_value::<RtRef>("rt")?.swap(s);
|
||||
let mod_ = LOADER.load(s)?;
|
||||
lua.named_registry_value::<RtRef>("rt")?.reset();
|
||||
|
||||
mod_.raw_set("_name", &name)?;
|
||||
Self::create_mt(lua, name, mod_)
|
||||
})?,
|
||||
)?;
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use mlua::{IntoLuaMulti, Table, UserData, Value};
|
||||
use tokio::{io::{AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
||||
use futures::future::try_join3;
|
||||
use mlua::{AnyUserData, IntoLuaMulti, Table, UserData, Value};
|
||||
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
||||
|
||||
use super::Status;
|
||||
use crate::process::Output;
|
||||
|
||||
pub struct Child {
|
||||
inner: tokio::process::Child,
|
||||
@ -26,8 +28,8 @@ impl UserData for Child {
|
||||
#[inline]
|
||||
// TODO: return mlua::String instead of String
|
||||
async fn read_line(me: &mut Child) -> (String, u8) {
|
||||
async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> {
|
||||
let mut r = t?;
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> {
|
||||
let mut r = r?;
|
||||
let mut buf = String::new();
|
||||
match r.read_line(&mut buf).await {
|
||||
Ok(0) | Err(_) => None,
|
||||
@ -43,8 +45,8 @@ impl UserData for Child {
|
||||
}
|
||||
|
||||
methods.add_async_method_mut("read", |_, me, len: usize| async move {
|
||||
async fn read(t: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
|
||||
let mut r = t?;
|
||||
async fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
|
||||
let mut r = r?;
|
||||
let mut buf = vec![0; len];
|
||||
match r.read(&mut buf).await {
|
||||
Ok(0) | Err(_) => return None,
|
||||
@ -73,6 +75,34 @@ impl UserData for Child {
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
});
|
||||
methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move {
|
||||
async fn read_to_end(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
|
||||
let mut vec = Vec::new();
|
||||
if let Some(r) = r.as_mut() {
|
||||
r.read_to_end(&mut vec).await?;
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
let mut me = ud.take::<Self>()?;
|
||||
let mut stdout_pipe = me.stdout.take();
|
||||
let mut stderr_pipe = me.stderr.take();
|
||||
|
||||
let stdout_fut = read_to_end(&mut stdout_pipe);
|
||||
let stderr_fut = read_to_end(&mut stderr_pipe);
|
||||
|
||||
let result = try_join3(me.inner.wait(), stdout_fut, stderr_fut).await;
|
||||
drop(stdout_pipe);
|
||||
drop(stderr_pipe);
|
||||
|
||||
match result {
|
||||
Ok((status, stdout, stderr)) => {
|
||||
(Output::new(std::process::Output { status, stdout, stderr }), Value::Nil)
|
||||
.into_lua_multi(lua)
|
||||
}
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
});
|
||||
methods.add_method_mut("start_kill", |lua, me, ()| match me.inner.start_kill() {
|
||||
Ok(_) => (true, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (false, e.raw_os_error()).into_lua_multi(lua),
|
||||
|
Loading…
Reference in New Issue
Block a user