Skip to content

Commit

Permalink
feat: plugin interface for key events via ya.which() (#617)
Browse files Browse the repository at this point in the history
  • Loading branch information
sxyazi authored Feb 2, 2024
1 parent 920afe2 commit b51d1f2
Show file tree
Hide file tree
Showing 15 changed files with 244 additions and 55 deletions.
9 changes: 9 additions & 0 deletions yazi-config/src/keymap/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
}
14 changes: 11 additions & 3 deletions yazi-config/src/keymap/key.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

use anyhow::bail;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use serde::Deserialize;
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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() {
Expand Down
33 changes: 33 additions & 0 deletions yazi-core/src/which/commands/callback.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 0 deletions yazi-core/src/which/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod callback;
mod show;
59 changes: 59 additions & 0 deletions yazi-core/src/which/commands/show.rs
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions yazi-core/src/which/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod commands;
mod which;

pub use which::*;
54 changes: 17 additions & 37 deletions yazi-core/src/which/which.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
15 changes: 14 additions & 1 deletion yazi-fm/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion yazi-fm/src/help/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion yazi-fm/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
5 changes: 4 additions & 1 deletion yazi-fm/src/which/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
58 changes: 58 additions & 0 deletions yazi-plugin/src/utils/layer.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
1 change: 1 addition & 0 deletions yazi-plugin/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod cache;
mod call;
mod image;
mod layer;
mod log;
mod plugin;
mod preview;
Expand Down
1 change: 1 addition & 0 deletions yazi-plugin/src/utils/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
Loading

0 comments on commit b51d1f2

Please sign in to comment.