mirror of
https://github.com/sxyazi/yazi.git
synced 2024-12-01 01:36:32 +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
|
pull-requests: write
|
||||||
discussions: write
|
discussions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
issue-inactive-days: "30"
|
issue-inactive-days: "30"
|
||||||
issue-comment: >
|
issue-comment: >
|
||||||
|
@ -81,7 +81,7 @@ keymap = [
|
|||||||
{ on = [ "s" ], run = "search fd", desc = "Search files by name using fd" },
|
{ 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 = [ "S" ], run = "search rg", desc = "Search files by content using ripgrep" },
|
||||||
{ on = [ "<C-s>" ], run = "search none", desc = "Cancel the ongoing search" },
|
{ 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" },
|
{ on = [ "Z" ], run = "jump fzf", desc = "Jump to a directory, or reveal a file using fzf" },
|
||||||
|
|
||||||
# Linemode
|
# Linemode
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use yazi_plugin::external::{self, FzfOpt, ZoxideOpt};
|
use std::time::Duration;
|
||||||
use yazi_proxy::{AppProxy, TabProxy, HIDER};
|
|
||||||
use yazi_shared::{event::Cmd, fs::ends_with_slash, Defer};
|
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;
|
use crate::tab::Tab;
|
||||||
|
|
||||||
@ -34,6 +36,21 @@ impl Tab {
|
|||||||
return;
|
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();
|
let cwd = self.current.cwd.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _permit = HIDER.acquire().await.unwrap();
|
let _permit = HIDER.acquire().await.unwrap();
|
||||||
@ -43,7 +60,7 @@ impl Tab {
|
|||||||
let result = if opt.type_ == OptType::Fzf {
|
let result = if opt.type_ == OptType::Fzf {
|
||||||
external::fzf(FzfOpt { cwd }).await
|
external::fzf(FzfOpt { cwd }).await
|
||||||
} else {
|
} else {
|
||||||
external::zoxide(ZoxideOpt { cwd }).await
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(url) = result else {
|
let Ok(url) = result else {
|
||||||
|
@ -16,7 +16,7 @@ impl<'a> Layout<'a> {
|
|||||||
|
|
||||||
pub(crate) fn available(area: Rect) -> Rect {
|
pub(crate) fn available(area: Rect) -> Rect {
|
||||||
let chunks =
|
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);
|
.split(area);
|
||||||
|
|
||||||
let chunks =
|
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 highlighter;
|
||||||
mod lsar;
|
mod lsar;
|
||||||
mod rg;
|
mod rg;
|
||||||
mod zoxide;
|
|
||||||
|
|
||||||
pub use fd::*;
|
pub use fd::*;
|
||||||
pub use fzf::*;
|
pub use fzf::*;
|
||||||
pub use highlighter::*;
|
pub use highlighter::*;
|
||||||
pub use lsar::*;
|
pub use lsar::*;
|
||||||
pub use rg::*;
|
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 use runtime::*;
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
crate::init_lua();
|
|
||||||
crate::loader::init();
|
crate::loader::init();
|
||||||
|
crate::init_lua();
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ impl Loader {
|
|||||||
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
|
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
|
||||||
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
|
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
|
||||||
"video" => include_bytes!("../../preset/plugins/video.lua"),
|
"video" => include_bytes!("../../preset/plugins/video.lua"),
|
||||||
|
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
|
||||||
_ => bail!("plugin not found: {name}"),
|
_ => 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;
|
use crate::RtRef;
|
||||||
|
|
||||||
pub(super) struct Require;
|
pub(super) struct Require;
|
||||||
@ -8,15 +9,16 @@ impl Require {
|
|||||||
pub(super) fn install(lua: &'static Lua) -> mlua::Result<()> {
|
pub(super) fn install(lua: &'static Lua) -> mlua::Result<()> {
|
||||||
let globals = lua.globals();
|
let globals = lua.globals();
|
||||||
|
|
||||||
let require = globals.raw_get::<_, Function>("require")?;
|
|
||||||
globals.raw_set(
|
globals.raw_set(
|
||||||
"require",
|
"require",
|
||||||
lua.create_function(move |lua, name: mlua::String| {
|
lua.create_function(|lua, name: mlua::String| {
|
||||||
lua.named_registry_value::<RtRef>("rt")?.swap(name.to_str()?);
|
let s = name.to_str()?;
|
||||||
let mod_: Table = require.call(&name)?;
|
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();
|
lua.named_registry_value::<RtRef>("rt")?.reset();
|
||||||
|
|
||||||
mod_.raw_set("_name", &name)?;
|
|
||||||
Self::create_mt(lua, name, mod_)
|
Self::create_mt(lua, name, mod_)
|
||||||
})?,
|
})?,
|
||||||
)?;
|
)?;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use mlua::{IntoLuaMulti, Table, UserData, Value};
|
use futures::future::try_join3;
|
||||||
use tokio::{io::{AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
use mlua::{AnyUserData, IntoLuaMulti, Table, UserData, Value};
|
||||||
|
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
||||||
|
|
||||||
use super::Status;
|
use super::Status;
|
||||||
|
use crate::process::Output;
|
||||||
|
|
||||||
pub struct Child {
|
pub struct Child {
|
||||||
inner: tokio::process::Child,
|
inner: tokio::process::Child,
|
||||||
@ -26,8 +28,8 @@ impl UserData for Child {
|
|||||||
#[inline]
|
#[inline]
|
||||||
// TODO: return mlua::String instead of String
|
// TODO: return mlua::String instead of String
|
||||||
async fn read_line(me: &mut Child) -> (String, u8) {
|
async fn read_line(me: &mut Child) -> (String, u8) {
|
||||||
async fn read(t: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> {
|
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<String> {
|
||||||
let mut r = t?;
|
let mut r = r?;
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
match r.read_line(&mut buf).await {
|
match r.read_line(&mut buf).await {
|
||||||
Ok(0) | Err(_) => None,
|
Ok(0) | Err(_) => None,
|
||||||
@ -43,8 +45,8 @@ impl UserData for Child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
methods.add_async_method_mut("read", |_, me, len: usize| async move {
|
methods.add_async_method_mut("read", |_, me, len: usize| async move {
|
||||||
async fn read(t: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
|
async fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
|
||||||
let mut r = t?;
|
let mut r = r?;
|
||||||
let mut buf = vec![0; len];
|
let mut buf = vec![0; len];
|
||||||
match r.read(&mut buf).await {
|
match r.read(&mut buf).await {
|
||||||
Ok(0) | Err(_) => return None,
|
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),
|
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() {
|
methods.add_method_mut("start_kill", |lua, me, ()| match me.inner.start_kill() {
|
||||||
Ok(_) => (true, Value::Nil).into_lua_multi(lua),
|
Ok(_) => (true, Value::Nil).into_lua_multi(lua),
|
||||||
Err(e) => (false, e.raw_os_error()).into_lua_multi(lua),
|
Err(e) => (false, e.raw_os_error()).into_lua_multi(lua),
|
||||||
|
Loading…
Reference in New Issue
Block a user