mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat!: enhance fzf integration (#2553)
This commit is contained in:
parent
8488ae8394
commit
1765aba684
9 changed files with 124 additions and 54 deletions
|
|
@ -82,8 +82,8 @@ keymap = [
|
|||
{ on = "s", run = "search --via=fd", desc = "Search files by name via fd" },
|
||||
{ on = "S", run = "search --via=rg", desc = "Search files by content via ripgrep" },
|
||||
{ on = "<C-s>", run = "escape --search", desc = "Cancel the ongoing search" },
|
||||
{ on = "z", run = "plugin zoxide", desc = "Jump to a directory via zoxide" },
|
||||
{ on = "Z", run = "plugin fzf", desc = "Jump to a file/directory via fzf" },
|
||||
{ on = "z", run = "plugin fzf", desc = "Jump to a file/directory via fzf" },
|
||||
{ on = "Z", run = "plugin zoxide", desc = "Jump to a directory via zoxide" },
|
||||
|
||||
# Linemode
|
||||
{ on = [ "m", "s" ], run = "linemode size", desc = "Linemode: size" },
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Mgr {
|
|||
if self.yanked.cut {
|
||||
tasks.file_cut(&src, dest, opt.force);
|
||||
|
||||
self.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src, false));
|
||||
self.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src));
|
||||
self.unyank(());
|
||||
} else {
|
||||
tasks.file_copy(&src, dest, opt.force, opt.follow);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ impl Mgr {
|
|||
#[yazi_codegen::command]
|
||||
pub fn remove_do(&mut self, opt: Opt, tasks: &Tasks) {
|
||||
self.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&opt.targets, false);
|
||||
t.selected.remove_many(&opt.targets);
|
||||
});
|
||||
|
||||
for u in &opt.targets {
|
||||
|
|
|
|||
|
|
@ -109,10 +109,9 @@ impl Tab {
|
|||
let urls: Vec<_> =
|
||||
indices.into_iter().filter_map(|i| self.current.files.get(i)).map(|f| &f.url).collect();
|
||||
|
||||
let same = !self.cwd().is_search();
|
||||
if !select {
|
||||
self.selected.remove_many(&urls, same);
|
||||
} else if self.selected.add_many(&urls, same) != urls.len() {
|
||||
self.selected.remove_many(&urls);
|
||||
} else if self.selected.add_many(&urls) != urls.len() {
|
||||
AppProxy::notify_warn(
|
||||
"Escape visual mode",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
use yazi_macro::render;
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::event::CmdCow;
|
||||
use yazi_shared::{event::CmdCow, url::Url};
|
||||
|
||||
use crate::tab::Tab;
|
||||
|
||||
struct Opt {
|
||||
urls: Vec<Url>,
|
||||
state: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<CmdCow> for Opt {
|
||||
fn from(c: CmdCow) -> Self {
|
||||
fn from(mut c: CmdCow) -> Self {
|
||||
let mut urls = Vec::with_capacity(c.len());
|
||||
for i in 0..c.len() {
|
||||
match c.take_url(i) {
|
||||
Some(url) => urls.push(url),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
urls,
|
||||
state: match c.str("state") {
|
||||
Some("on") => Some(true),
|
||||
Some("off") => Some(false),
|
||||
|
|
@ -19,27 +29,38 @@ impl From<CmdCow> for Opt {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<bool>> for Opt {
|
||||
fn from(state: Option<bool>) -> Self { Self { state } }
|
||||
fn from(state: Option<bool>) -> Self { Self { urls: vec![], state } }
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
#[yazi_codegen::command]
|
||||
pub fn toggle_all(&mut self, opt: Opt) {
|
||||
let iter = self.current.files.iter().map(|f| &f.url);
|
||||
let (removal, addition): (Vec<_>, Vec<_>) = match opt.state {
|
||||
Some(true) => (vec![], iter.collect()),
|
||||
Some(false) => (iter.collect(), vec![]),
|
||||
None => iter.partition(|&u| self.selected.contains_key(u)),
|
||||
use yazi_shared::Either::*;
|
||||
|
||||
let it = self.current.files.iter().map(|f| &f.url);
|
||||
let either = match opt.state {
|
||||
Some(true) if opt.urls.is_empty() => Left((vec![], it.collect())),
|
||||
Some(true) => Right((vec![], opt.urls)),
|
||||
Some(false) if opt.urls.is_empty() => Left((it.collect(), vec![])),
|
||||
Some(false) => Right((opt.urls, vec![])),
|
||||
None if opt.urls.is_empty() => Left(it.partition(|&u| self.selected.contains_key(u))),
|
||||
None => Right(opt.urls.into_iter().partition(|u| self.selected.contains_key(u))),
|
||||
};
|
||||
|
||||
let same = !self.cwd().is_search();
|
||||
render!(self.selected.remove_many(&removal, same) > 0);
|
||||
let warn = match either {
|
||||
Left((removal, addition)) => {
|
||||
render!(self.selected.remove_many(&removal) > 0);
|
||||
render!(self.selected.add_many(&addition), > 0) != addition.len()
|
||||
}
|
||||
Right((removal, addition)) => {
|
||||
render!(self.selected.remove_many(&removal) > 0);
|
||||
render!(self.selected.add_many(&addition), > 0) != addition.len()
|
||||
}
|
||||
};
|
||||
|
||||
let added = self.selected.add_many(&addition, same);
|
||||
render!(added > 0);
|
||||
|
||||
if added != addition.len() {
|
||||
if warn {
|
||||
AppProxy::notify_warn(
|
||||
"Toggle all",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
|
|
|
|||
|
|
@ -20,11 +20,7 @@ impl Selected {
|
|||
#[inline]
|
||||
pub fn add(&mut self, url: &Url) -> bool { self.add_same(&[url]) == 1 }
|
||||
|
||||
pub fn add_many(&mut self, urls: &[impl AsRef<Url>], same: bool) -> usize {
|
||||
if same {
|
||||
return self.add_same(urls);
|
||||
}
|
||||
|
||||
pub fn add_many(&mut self, urls: &[impl AsRef<Url>]) -> usize {
|
||||
let mut grouped: HashMap<_, Vec<_>> = Default::default();
|
||||
for u in urls {
|
||||
if let Some(p) = u.as_ref().parent_url() {
|
||||
|
|
@ -66,22 +62,19 @@ impl Selected {
|
|||
#[inline]
|
||||
pub fn remove(&mut self, url: &Url) -> bool { self.remove_same(&[url]) == 1 }
|
||||
|
||||
pub fn remove_many(&mut self, urls: &[impl AsRef<Url>], same: bool) -> usize {
|
||||
let affected = if same {
|
||||
self.remove_same(urls)
|
||||
} else {
|
||||
let mut grouped: HashMap<_, Vec<_>> = Default::default();
|
||||
for u in urls {
|
||||
if let Some(p) = u.as_ref().parent_url() {
|
||||
grouped.entry(p).or_default().push(u);
|
||||
}
|
||||
pub fn remove_many(&mut self, urls: &[impl AsRef<Url>]) -> usize {
|
||||
let mut grouped: HashMap<_, Vec<_>> = Default::default();
|
||||
for u in urls {
|
||||
if let Some(p) = u.as_ref().parent_url() {
|
||||
grouped.entry(p).or_default().push(u);
|
||||
}
|
||||
grouped.into_values().map(|v| self.remove_same(&v)).sum()
|
||||
};
|
||||
}
|
||||
|
||||
let affected = grouped.into_values().map(|v| self.remove_same(&v)).sum();
|
||||
if affected > 0 {
|
||||
self.inner.sort_unstable_by(|_, a, _, b| a.cmp(b));
|
||||
}
|
||||
|
||||
affected
|
||||
}
|
||||
|
||||
|
|
@ -113,10 +106,10 @@ impl Selected {
|
|||
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||
let (removal, addition) = op.diff_recoverable(|u| self.contains_key(u));
|
||||
if !removal.is_empty() {
|
||||
self.remove_many(&removal, !op.cwd().is_search());
|
||||
self.remove_many(&removal);
|
||||
}
|
||||
if !addition.is_empty() {
|
||||
self.add_many(&addition, !op.cwd().is_search());
|
||||
self.add_many(&addition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ macro_rules! render {
|
|||
render!();
|
||||
}
|
||||
};
|
||||
($left:expr, > $right:expr) => {{
|
||||
let val = $left;
|
||||
if val > $right {
|
||||
render!();
|
||||
}
|
||||
val
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
|||
|
|
@ -1,29 +1,73 @@
|
|||
local state = ya.sync(function() return cx.active.current.cwd end)
|
||||
local M = {}
|
||||
|
||||
local function fail(s, ...) ya.notify { title = "Fzf", content = string.format(s, ...), timeout = 5, level = "error" } end
|
||||
local state = ya.sync(function()
|
||||
local selected = {}
|
||||
for _, url in pairs(cx.active.selected) do
|
||||
selected[#selected + 1] = url
|
||||
end
|
||||
return cx.active.current.cwd, selected
|
||||
end)
|
||||
|
||||
function M:entry()
|
||||
ya.mgr_emit("escape", { visual = true })
|
||||
|
||||
local function entry()
|
||||
local _permit = ya.hide()
|
||||
local cwd = tostring(state())
|
||||
local cwd, selected = state()
|
||||
|
||||
local child, err =
|
||||
Command("fzf"):cwd(cwd):stdin(Command.INHERIT):stdout(Command.PIPED):stderr(Command.INHERIT):spawn()
|
||||
local output, err = M.run_with(cwd, selected)
|
||||
if not output then
|
||||
return ya.notify { title = "Fzf", content = tostring(err), timeout = 5, level = "error" }
|
||||
end
|
||||
|
||||
local urls = M.split_urls(cwd, output)
|
||||
if #urls == 1 then
|
||||
local cha = #selected == 0 and fs.cha(urls[1])
|
||||
ya.mgr_emit(cha and cha.is_dir and "cd" or "reveal", { urls[1] })
|
||||
elseif #urls > 1 then
|
||||
urls.state = #selected > 0 and "off" or "on"
|
||||
ya.mgr_emit("toggle_all", urls)
|
||||
end
|
||||
end
|
||||
|
||||
function M.run_with(cwd, selected)
|
||||
local child, err = Command("fzf")
|
||||
:arg("-m")
|
||||
:cwd(tostring(cwd))
|
||||
:stdin(#selected > 0 and Command.PIPED or Command.INHERIT)
|
||||
:stdout(Command.PIPED)
|
||||
:spawn()
|
||||
|
||||
if not child then
|
||||
return fail("Failed to start `fzf`, error: " .. err)
|
||||
return nil, Err("Failed to start `fzf`, error: %s", err)
|
||||
end
|
||||
|
||||
for _, u in ipairs(selected) do
|
||||
child:write_all(string.format("%s\n", u))
|
||||
end
|
||||
if #selected > 0 then
|
||||
child:flush()
|
||||
end
|
||||
|
||||
local output, err = child:wait_with_output()
|
||||
if not output then
|
||||
return fail("Cannot read `fzf` output, error: " .. err)
|
||||
return nil, Err("Cannot read `fzf` output, error: %s", err)
|
||||
elseif not output.status.success and output.status.code ~= 130 then
|
||||
return fail("`fzf` exited with error code %s", output.status.code)
|
||||
end
|
||||
|
||||
local target = output.stdout:gsub("\n$", "")
|
||||
if target ~= "" then
|
||||
ya.mgr_emit(target:find("[/\\]$") and "cd" or "reveal", { target })
|
||||
return nil, Err("`fzf` exited with error code %s", output.status.code)
|
||||
end
|
||||
return output.stdout, nil
|
||||
end
|
||||
|
||||
return { entry = entry }
|
||||
function M.split_urls(cwd, output)
|
||||
local t = {}
|
||||
for line in output:gmatch("[^\r\n]+") do
|
||||
local u = Url(line)
|
||||
if u.is_absolute then
|
||||
t[#t + 1] = u
|
||||
else
|
||||
t[#t + 1] = cwd:join(u)
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ impl Cmd {
|
|||
me
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize { self.args.len() }
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool { self.args.is_empty() }
|
||||
|
||||
// --- With
|
||||
#[inline]
|
||||
pub fn with(mut self, name: impl Into<DataKey>, value: impl ToString) -> Self {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue