Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugin interface for key events via ya.which() #617

Merged
merged 2 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading