mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-23 07:41:03 +00:00
feat: plugin interface for key events via ya.which() (#617)
This commit is contained in:
parent
920afe29bd
commit
b51d1f26d0
15 changed files with 244 additions and 55 deletions
|
|
@ -66,3 +66,12 @@ impl Deref for ControlCow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlCow {
|
||||
pub fn into_seq(self) -> VecDeque<Cmd> {
|
||||
match self {
|
||||
Self::Owned(c) => c.exec.into(),
|
||||
Self::Borrowed(c) => c.to_seq(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::bail;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -58,10 +60,10 @@ impl From<KeyEvent> for Key {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Key {
|
||||
type Error = anyhow::Error;
|
||||
impl FromStr for Key {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.is_empty() {
|
||||
bail!("empty key")
|
||||
}
|
||||
|
|
@ -124,6 +126,12 @@ impl TryFrom<String> for Key {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Key {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(&s) }
|
||||
}
|
||||
|
||||
impl ToString for Key {
|
||||
fn to_string(&self) -> String {
|
||||
if let Some(c) = self.plain() {
|
||||
|
|
|
|||
33
yazi-core/src/which/commands/callback.rs
Normal file
33
yazi-core/src/which/commands/callback.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use crate::which::Which;
|
||||
|
||||
pub struct Opt {
|
||||
tx: mpsc::Sender<usize>,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl TryFrom<Cmd> for Opt {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
tx: c.take_data().ok_or(())?,
|
||||
idx: c.take_first().and_then(|s| s.parse().ok()).ok_or(())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Which {
|
||||
pub fn callback(&mut self, opt: impl TryInto<Opt>) {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if opt.tx.try_send(opt.idx).is_err() {
|
||||
error!("callback: send error");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
yazi-core/src/which/commands/mod.rs
Normal file
2
yazi-core/src/which/commands/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mod callback;
|
||||
mod show;
|
||||
59
yazi-core/src/which/commands/show.rs
Normal file
59
yazi-core/src/which/commands/show.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use yazi_config::{keymap::{Control, Key}, KEYMAP};
|
||||
use yazi_shared::{event::Cmd, render, Layer};
|
||||
|
||||
use crate::which::Which;
|
||||
|
||||
pub struct Opt {
|
||||
cands: Vec<Control>,
|
||||
layer: Layer,
|
||||
silent: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<Cmd> for Opt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
cands: c.take_data().unwrap_or_default(),
|
||||
layer: Layer::from_str(&c.take_name("layer").unwrap_or_default())?,
|
||||
silent: c.named.contains_key("silent"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Which {
|
||||
pub fn show(&mut self, opt: impl TryInto<Opt>) {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if opt.cands.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.layer = opt.layer;
|
||||
self.times = 0;
|
||||
self.cands = opt.cands.into_iter().map(|c| c.into()).collect();
|
||||
|
||||
self.visible = true;
|
||||
self.silent = opt.silent;
|
||||
render!();
|
||||
}
|
||||
|
||||
pub fn show_with(&mut self, key: &Key, layer: Layer) {
|
||||
self.layer = layer;
|
||||
self.times = 1;
|
||||
self.cands = KEYMAP
|
||||
.get(layer)
|
||||
.iter()
|
||||
.filter(|c| c.on.len() > 1 && &c.on[0] == key)
|
||||
.map(|c| c.into())
|
||||
.collect();
|
||||
|
||||
self.visible = true;
|
||||
self.silent = false;
|
||||
render!();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod commands;
|
||||
mod which;
|
||||
|
||||
pub use which::*;
|
||||
|
|
|
|||
|
|
@ -1,60 +1,40 @@
|
|||
use yazi_config::{keymap::{Control, ControlCow, Key}, KEYMAP};
|
||||
use yazi_config::keymap::{ControlCow, Key};
|
||||
use yazi_shared::{emit, render, Layer};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Which {
|
||||
layer: Layer,
|
||||
pub times: usize,
|
||||
pub cands: Vec<ControlCow>,
|
||||
pub(super) layer: Layer,
|
||||
pub times: usize,
|
||||
pub cands: Vec<ControlCow>,
|
||||
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Default for Which {
|
||||
fn default() -> Self {
|
||||
Self { layer: Layer::Manager, times: 0, cands: Default::default(), visible: false }
|
||||
}
|
||||
pub silent: bool,
|
||||
}
|
||||
|
||||
impl Which {
|
||||
pub fn show(&mut self, key: &Key, layer: Layer) {
|
||||
self.layer = layer;
|
||||
self.times = 1;
|
||||
self.cands = KEYMAP
|
||||
.get(layer)
|
||||
.iter()
|
||||
.filter(|c| c.on.len() > 1 && &c.on[0] == key)
|
||||
.map(|c| c.into())
|
||||
.collect();
|
||||
|
||||
self.visible = true;
|
||||
render!();
|
||||
}
|
||||
|
||||
pub fn show_with(&mut self, cands: Vec<Control>, layer: Layer) {
|
||||
self.layer = layer;
|
||||
fn reset(&mut self) {
|
||||
self.times = 0;
|
||||
self.cands = cands.into_iter().map(|c| c.into()).collect();
|
||||
self.cands.clear();
|
||||
|
||||
self.visible = true;
|
||||
render!();
|
||||
self.visible = false;
|
||||
self.silent = false;
|
||||
}
|
||||
|
||||
pub fn type_(&mut self, key: Key) -> bool {
|
||||
self.cands.retain(|c| c.on.len() > self.times && c.on[self.times] == key);
|
||||
self.times += 1;
|
||||
|
||||
if self.cands.is_empty() {
|
||||
self.visible = false;
|
||||
self.reset();
|
||||
} else if self.cands.len() == 1 {
|
||||
self.visible = false;
|
||||
emit!(Seq(self.cands[0].to_seq(), self.layer));
|
||||
} else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times + 1) {
|
||||
self.visible = false;
|
||||
emit!(Seq(self.cands[i].to_seq(), self.layer));
|
||||
emit!(Seq(self.cands.remove(0).into_seq(), self.layer));
|
||||
self.reset();
|
||||
} else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times) {
|
||||
emit!(Seq(self.cands.remove(i).into_seq(), self.layer));
|
||||
self.reset();
|
||||
}
|
||||
|
||||
self.times += 1;
|
||||
render!();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl<'a> Executor<'a> {
|
|||
Layer::Input => self.input(cmd),
|
||||
Layer::Help => self.help(cmd),
|
||||
Layer::Completion => self.completion(cmd),
|
||||
Layer::Which => unreachable!(),
|
||||
Layer::Which => self.which(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,4 +276,17 @@ impl<'a> Executor<'a> {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn which(&mut self, cmd: Cmd) {
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
if cmd.name == stringify!($name) {
|
||||
return self.app.cx.which.$name(cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
on!(show);
|
||||
on!(callback);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl<'a> Widget for Layout<'a> {
|
|||
widgets::Clear.render(area, buf);
|
||||
|
||||
let help = &self.cx.help;
|
||||
Paragraph::new(help.keyword().unwrap_or_else(|| format!("{}.help", help.layer)))
|
||||
Paragraph::new(help.keyword().unwrap_or_else(|| format!("{}.help", help.layer.to_string())))
|
||||
.style(THEME.help.footer.into())
|
||||
.render(chunks[1], buf);
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ impl<'a> Router<'a> {
|
|||
}
|
||||
|
||||
if on.len() > 1 {
|
||||
self.app.cx.which.show(&key, layer);
|
||||
self.app.cx.which.show_with(&key, layer);
|
||||
} else {
|
||||
emit!(Seq(ctrl.to_seq(), layer));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@ impl<'a> Which<'a> {
|
|||
impl Widget for Which<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let which = &self.cx.which;
|
||||
let cols = THEME.which.cols as usize;
|
||||
if which.silent {
|
||||
return;
|
||||
}
|
||||
|
||||
let cols = THEME.which.cols as usize;
|
||||
let height = area.height.min(which.cands.len().div_ceil(cols) as u16 + PADDING_Y * 2);
|
||||
let area = Rect {
|
||||
x: PADDING_X.min(area.width),
|
||||
|
|
|
|||
58
yazi-plugin/src/utils/layer.rs
Normal file
58
yazi-plugin/src/utils/layer.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use mlua::{ExternalError, ExternalResult, Lua, Table, Value};
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_config::keymap::{Control, Key};
|
||||
use yazi_shared::{emit, event::Cmd, Layer};
|
||||
|
||||
use super::Utils;
|
||||
|
||||
impl Utils {
|
||||
fn parse_keys(value: Value) -> mlua::Result<Vec<Key>> {
|
||||
Ok(match value {
|
||||
Value::String(s) => {
|
||||
vec![Key::from_str(s.to_str()?).into_lua_err()?]
|
||||
}
|
||||
Value::Table(t) => {
|
||||
let mut v = Vec::with_capacity(10);
|
||||
for s in t.sequence_values::<mlua::String>() {
|
||||
v.push(Key::from_str(s?.to_str()?).into_lua_err()?);
|
||||
}
|
||||
v
|
||||
}
|
||||
_ => Err("invalid `on`".into_lua_err())?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn layer(lua: &Lua, ya: &Table) -> mlua::Result<()> {
|
||||
ya.set(
|
||||
"which",
|
||||
lua.create_async_function(|_, t: Table| async move {
|
||||
let (tx, mut rx) = mpsc::channel::<usize>(1);
|
||||
|
||||
let mut cands = Vec::with_capacity(30);
|
||||
for (i, cand) in t.get::<_, Table>("cands")?.sequence_values::<Table>().enumerate() {
|
||||
let cand = cand?;
|
||||
cands.push(Control {
|
||||
on: Self::parse_keys(cand.get("on")?)?,
|
||||
exec: vec![Cmd::args("callback", vec![i.to_string()]).with_data(tx.clone())],
|
||||
desc: cand.get("desc").ok(),
|
||||
});
|
||||
}
|
||||
|
||||
drop(tx);
|
||||
emit!(Call(
|
||||
Cmd::new("show")
|
||||
.with("layer", Layer::Which)
|
||||
.with_bool("silent", t.get("silent").unwrap_or_default())
|
||||
.with_data(cands),
|
||||
Layer::Which
|
||||
));
|
||||
|
||||
Ok(rx.recv().await.map(|idx| idx + 1))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
mod cache;
|
||||
mod call;
|
||||
mod image;
|
||||
mod layer;
|
||||
mod log;
|
||||
mod plugin;
|
||||
mod preview;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
|
|||
Utils::cache(lua, &ya)?;
|
||||
Utils::call(lua, &ya)?;
|
||||
Utils::image(lua, &ya)?;
|
||||
Utils::layer(lua, &ya)?;
|
||||
Utils::log(lua, &ya)?;
|
||||
Utils::plugin(lua, &ya)?;
|
||||
Utils::preview(lua, &ya)?;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::bail;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum Layer {
|
||||
|
|
@ -13,17 +15,36 @@ pub enum Layer {
|
|||
Which,
|
||||
}
|
||||
|
||||
impl Display for Layer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl ToString for Layer {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::App => write!(f, "app"),
|
||||
Self::Manager => write!(f, "manager"),
|
||||
Self::Tasks => write!(f, "tasks"),
|
||||
Self::Select => write!(f, "select"),
|
||||
Self::Input => write!(f, "input"),
|
||||
Self::Help => write!(f, "help"),
|
||||
Self::Completion => write!(f, "completion"),
|
||||
Self::Which => write!(f, "which"),
|
||||
Self::App => "app",
|
||||
Self::Manager => "manager",
|
||||
Self::Tasks => "tasks",
|
||||
Self::Select => "select",
|
||||
Self::Input => "input",
|
||||
Self::Help => "help",
|
||||
Self::Completion => "completion",
|
||||
Self::Which => "which",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Layer {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"app" => Self::App,
|
||||
"manager" => Self::Manager,
|
||||
"tasks" => Self::Tasks,
|
||||
"select" => Self::Select,
|
||||
"input" => Self::Input,
|
||||
"help" => Self::Help,
|
||||
"completion" => Self::Completion,
|
||||
"which" => Self::Which,
|
||||
_ => bail!("invalid layer: {s}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue