mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat: dynamic keymap Lua API (#4031)
This commit is contained in:
parent
4086fbb166
commit
07a5deb109
28 changed files with 317 additions and 169 deletions
|
|
@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- Drag and drop ([#4005])
|
||||
- Bulk create ([#3793])
|
||||
- Dynamic keymap Lua API ([#4031])
|
||||
- Image preview with Überzug++ on Niri ([#3990])
|
||||
- New gait for input `backward` and `forward` actions ([#4012])
|
||||
|
||||
|
|
@ -1742,3 +1743,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#4005]: https://github.com/sxyazi/yazi/pull/4005
|
||||
[#4012]: https://github.com/sxyazi/yazi/pull/4012
|
||||
[#4022]: https://github.com/sxyazi/yazi/pull/4022
|
||||
[#4031]: https://github.com/sxyazi/yazi/pull/4031
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ This guide will help you understand how to contribute to the project.
|
|||
3. [Development Setup](#development-setup)
|
||||
4. [How to Contribute](#how-to-contribute)
|
||||
5. [Pull Requests](#pull-requests)
|
||||
6. [AI Policy](#ai-policy)
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
|
|||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -5674,6 +5674,7 @@ dependencies = [
|
|||
"ordered-float 5.3.0",
|
||||
"paste",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ impl UserData for Which {
|
|||
fields.add_cached_field("tx", |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
|
||||
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
|
||||
fields.add_cached_field("cands", |lua, me| {
|
||||
lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::keymap::ChordCow))
|
||||
lua.create_sequence_from(me.inner.cands.iter().map(yazi_binding::keymap::Chord::from))
|
||||
});
|
||||
|
||||
fields.add_field_method_get("active", |_, me| Ok(me.inner.active));
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ mlua = { workspace = true }
|
|||
ordered-float = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{mem, ops::{Deref, DerefMut}};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use mlua::{UserData, UserDataFields};
|
||||
use mlua::{FromLua, Lua, UserData, UserDataFields, Value};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::event::Cmd;
|
||||
|
|
@ -19,12 +19,34 @@ impl DerefMut for Action {
|
|||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||
}
|
||||
|
||||
impl From<Action> for yazi_shared::event::Action {
|
||||
fn from(value: Action) -> Self { value.inner }
|
||||
}
|
||||
|
||||
impl From<Action> for yazi_shared::event::Actions {
|
||||
fn from(value: Action) -> Self { Self::from(value.inner) }
|
||||
}
|
||||
|
||||
impl Action {
|
||||
pub fn new(inner: impl Into<yazi_shared::event::Action>) -> Self { Self { inner: inner.into() } }
|
||||
}
|
||||
|
||||
impl UserData for Action {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field_mut("cmd", |_, me| Ok(Cmd::new(mem::take(&mut me.cmd))));
|
||||
impl FromLua for Action {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
Ok(match value {
|
||||
Value::String(s) => Self { inner: s.to_str()?.parse()? },
|
||||
Value::UserData(ud) => ud.take()?,
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "Action".to_owned(),
|
||||
message: Some("expected a string or an Action".to_string()),
|
||||
})?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Action {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("cmd", |_, me| Ok(Cmd::new(me.cmd.clone())));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
yazi-binding/src/event/actions.rs
Normal file
50
yazi-binding/src/event/actions.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
|
||||
use crate::event::Action;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Actions {
|
||||
inner: yazi_shared::event::Actions,
|
||||
}
|
||||
|
||||
impl Deref for Actions {
|
||||
type Target = yazi_shared::event::Actions;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl DerefMut for Actions {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
||||
}
|
||||
|
||||
impl From<Actions> for yazi_shared::event::Actions {
|
||||
fn from(value: Actions) -> Self { value.inner }
|
||||
}
|
||||
|
||||
impl Actions {
|
||||
pub fn new(inner: yazi_shared::event::Actions) -> Self { Self { inner } }
|
||||
}
|
||||
|
||||
impl FromLua for Actions {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
let inner = match value {
|
||||
Value::Table(t) => t
|
||||
.sequence_values::<Action>()
|
||||
.map(|a| a.map(Into::into))
|
||||
.collect::<mlua::Result<Vec<_>>>()?
|
||||
.into(),
|
||||
v @ Value::String(_) => Action::from_lua(v, lua)?.into(),
|
||||
_ => Err("expected a string or a table of actions".into_lua_err())?,
|
||||
};
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for Actions {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
lua.create_sequence_from(self.inner.into_iter().map(Action::new))?.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(action cmd);
|
||||
yazi_macro::mod_flat!(action actions cmd);
|
||||
|
|
|
|||
|
|
@ -1,33 +1,61 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
use std::ops::Deref;
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::FromLuaOwned;
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::{Id, Iter, event::Action, keymap::Key};
|
||||
use crate::{Id, Iter, event::Actions, keymap::Key};
|
||||
|
||||
#[derive(FromLuaOwned)]
|
||||
pub struct Chord {
|
||||
inner: Arc<yazi_config::keymap::Chord>,
|
||||
inner: yazi_config::keymap::ChordArc,
|
||||
}
|
||||
|
||||
impl Deref for Chord {
|
||||
type Target = Arc<yazi_config::keymap::Chord>;
|
||||
type Target = yazi_config::keymap::ChordArc;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl From<Chord> for Arc<yazi_config::keymap::Chord> {
|
||||
impl From<&yazi_config::keymap::ChordArc> for Chord {
|
||||
fn from(value: &yazi_config::keymap::ChordArc) -> Self { Self { inner: value.clone() } }
|
||||
}
|
||||
|
||||
impl From<Chord> for yazi_config::keymap::ChordArc {
|
||||
fn from(value: Chord) -> Self { value.inner }
|
||||
}
|
||||
|
||||
impl Chord {
|
||||
pub fn new(inner: impl Into<Arc<yazi_config::keymap::Chord>>) -> Self {
|
||||
pub fn new(inner: impl Into<yazi_config::keymap::ChordArc>) -> Self {
|
||||
Self { inner: inner.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for Chord {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
Ok(Self::new(lua.from_value::<yazi_config::keymap::Chord>(value)?))
|
||||
impl TryFrom<(Value, yazi_shared::Layer)> for Chord {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from((value, layer): (Value, yazi_shared::Layer)) -> Result<Self, Self::Error> {
|
||||
use yazi_config::keymap::Chord as C;
|
||||
use yazi_shared::Layer as L;
|
||||
|
||||
let de = mlua::serde::Deserializer::new(value);
|
||||
let inner = match layer {
|
||||
L::Null => C::<{ L::Null as u8 }>::deserialize(de)?.into(),
|
||||
L::App => C::<{ L::App as u8 }>::deserialize(de)?.into(),
|
||||
L::Mgr => C::<{ L::Mgr as u8 }>::deserialize(de)?.into(),
|
||||
L::Tasks => C::<{ L::Tasks as u8 }>::deserialize(de)?.into(),
|
||||
L::Spot => C::<{ L::Spot as u8 }>::deserialize(de)?.into(),
|
||||
L::Pick => C::<{ L::Pick as u8 }>::deserialize(de)?.into(),
|
||||
L::Input => C::<{ L::Input as u8 }>::deserialize(de)?.into(),
|
||||
L::Confirm => C::<{ L::Confirm as u8 }>::deserialize(de)?.into(),
|
||||
L::Help => C::<{ L::Help as u8 }>::deserialize(de)?.into(),
|
||||
L::Cmp => C::<{ L::Cmp as u8 }>::deserialize(de)?.into(),
|
||||
L::Which => C::<{ L::Which as u8 }>::deserialize(de)?.into(),
|
||||
L::Notify => C::<{ L::Notify as u8 }>::deserialize(de)?.into(),
|
||||
};
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,9 +66,7 @@ impl UserData for Chord {
|
|||
fields
|
||||
.add_cached_field("on", |lua, me| lua.create_sequence_from(me.on.iter().copied().map(Key)));
|
||||
|
||||
fields.add_cached_field("run", |lua, me| {
|
||||
lua.create_sequence_from(me.run.iter().cloned().map(Action::new))
|
||||
});
|
||||
fields.add_cached_field("run", |_, me| Ok(Actions::new(me.run.clone())));
|
||||
|
||||
fields.add_cached_field("desc", |lua, me| lua.create_string(&me.desc));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
use mlua::UserData;
|
||||
use yazi_codegen::FromLuaOwned;
|
||||
|
||||
#[derive(Clone, FromLuaOwned)]
|
||||
pub struct ChordCow(pub yazi_config::keymap::ChordCow);
|
||||
|
||||
impl From<yazi_config::keymap::ChordCow> for ChordCow {
|
||||
fn from(value: yazi_config::keymap::ChordCow) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl From<ChordCow> for yazi_config::keymap::ChordCow {
|
||||
fn from(value: ChordCow) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl UserData for ChordCow {}
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{ExternalError, ExternalResult, MetaMethod, UserData, UserDataMethods};
|
||||
use mlua::{ExternalError, ExternalResult, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use yazi_shim::mlua::DeserializeOverLua;
|
||||
|
||||
use super::{Chord, ChordMatcher};
|
||||
use crate::keymap::ChordIter;
|
||||
use crate::{event::Actions, keymap::ChordIter};
|
||||
|
||||
pub struct Chords {
|
||||
inner: &'static yazi_config::keymap::Chords,
|
||||
pub(super) inner: &'static yazi_config::keymap::Chords,
|
||||
pub(super) layer: yazi_shared::Layer,
|
||||
}
|
||||
|
||||
impl Deref for Chords {
|
||||
|
|
@ -15,10 +17,6 @@ impl Deref for Chords {
|
|||
fn deref(&self) -> &Self::Target { self.inner }
|
||||
}
|
||||
|
||||
impl Chords {
|
||||
pub fn new(inner: &'static yazi_config::keymap::Chords) -> Self { Self { inner } }
|
||||
}
|
||||
|
||||
impl UserData for Chords {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("match", |_, me, matcher: Option<ChordMatcher>| {
|
||||
|
|
@ -32,14 +30,16 @@ impl UserData for Chords {
|
|||
})
|
||||
});
|
||||
|
||||
methods.add_method("insert", |_, me, (index, chord): (isize, Chord)| {
|
||||
methods.add_method("insert", |_, me, (index, value): (isize, Value)| {
|
||||
let index = match index {
|
||||
1.. => index - 1,
|
||||
0 => return Err("index must be 1-based or negative".into_lua_err()),
|
||||
_ => index,
|
||||
};
|
||||
|
||||
let chord = Chord::try_from((value, me.layer))?;
|
||||
me.insert(index, chord.clone()).into_lua_err()?;
|
||||
|
||||
Ok(chord)
|
||||
});
|
||||
|
||||
|
|
@ -48,6 +48,22 @@ impl UserData for Chords {
|
|||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_method("update", |_, me, (matcher, table): (ChordMatcher, Table)| {
|
||||
let mut run: Option<Actions> = table.raw_get("run")?;
|
||||
if let Some(run) = &mut run {
|
||||
table.raw_remove("run")?;
|
||||
run.set(me.layer, yazi_shared::Source::Key);
|
||||
}
|
||||
|
||||
me.update(matcher.0, |mut chord| {
|
||||
chord = chord.deserialize_over_lua(&table)?;
|
||||
if let Some(run) = &run {
|
||||
chord.run = run.clone().into();
|
||||
}
|
||||
Ok(chord)
|
||||
})
|
||||
});
|
||||
|
||||
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.load().len()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(chord chord_cow chords key section);
|
||||
yazi_macro::mod_flat!(chord chords key section);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ use std::ops::Deref;
|
|||
use anyhow::bail;
|
||||
use mlua::{UserData, UserDataFields};
|
||||
use yazi_config::KEYMAP;
|
||||
use yazi_shared::Layer;
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::keymap::Chords;
|
||||
|
||||
pub struct KeymapSection {
|
||||
inner: &'static yazi_config::keymap::KeymapSection,
|
||||
layer: yazi_shared::Layer,
|
||||
}
|
||||
|
||||
impl Deref for KeymapSection {
|
||||
|
|
@ -18,30 +18,32 @@ impl Deref for KeymapSection {
|
|||
fn deref(&self) -> &Self::Target { self.inner }
|
||||
}
|
||||
|
||||
impl TryFrom<Layer> for KeymapSection {
|
||||
impl TryFrom<yazi_shared::Layer> for KeymapSection {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: Layer) -> Result<Self, Self::Error> {
|
||||
let inner = match value {
|
||||
Layer::Null | Layer::App => bail!("invalid layer"),
|
||||
Layer::Mgr => KEYMAP.mgr.as_erased(),
|
||||
Layer::Tasks => KEYMAP.tasks.as_erased(),
|
||||
Layer::Spot => KEYMAP.spot.as_erased(),
|
||||
Layer::Pick => KEYMAP.pick.as_erased(),
|
||||
Layer::Input => KEYMAP.input.as_erased(),
|
||||
Layer::Confirm => KEYMAP.confirm.as_erased(),
|
||||
Layer::Help => KEYMAP.help.as_erased(),
|
||||
Layer::Cmp => KEYMAP.cmp.as_erased(),
|
||||
Layer::Which => bail!("invalid layer"),
|
||||
Layer::Notify => bail!("invalid layer"),
|
||||
fn try_from(layer: yazi_shared::Layer) -> Result<Self, Self::Error> {
|
||||
use yazi_shared::Layer as L;
|
||||
|
||||
let inner = match layer {
|
||||
L::Null | L::App => bail!("invalid layer"),
|
||||
L::Mgr => KEYMAP.mgr.as_erased(),
|
||||
L::Tasks => KEYMAP.tasks.as_erased(),
|
||||
L::Spot => KEYMAP.spot.as_erased(),
|
||||
L::Pick => KEYMAP.pick.as_erased(),
|
||||
L::Input => KEYMAP.input.as_erased(),
|
||||
L::Confirm => KEYMAP.confirm.as_erased(),
|
||||
L::Help => KEYMAP.help.as_erased(),
|
||||
L::Cmp => KEYMAP.cmp.as_erased(),
|
||||
L::Which => bail!("invalid layer"),
|
||||
L::Notify => bail!("invalid layer"),
|
||||
};
|
||||
|
||||
Ok(Self { inner })
|
||||
Ok(Self { inner, layer })
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for KeymapSection {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_cached_field("rules", |_, me| Ok(Chords::new(me.inner)));
|
||||
fields.add_cached_field("rules", |_, me| Ok(Chords { inner: me.inner, layer: me.layer }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ use std::{borrow::Cow, hash::{Hash, Hasher}, sync::{Arc, OnceLock}};
|
|||
use regex::Regex;
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_shared::{Id, Layer, event::{Actions, deserialize_actions}};
|
||||
|
||||
use super::{Key, ids::chord_id};
|
||||
use crate::{Mixable, Platform, keymap::Chords};
|
||||
use crate::{Mixable, Platform, keymap::{ChordArc, Chords}};
|
||||
|
||||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[derive(Debug, Default, Deserialize, DeserializeOver2)]
|
||||
pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
|
||||
#[serde(skip, default = "chord_id")]
|
||||
pub id: Id,
|
||||
|
|
@ -47,10 +48,6 @@ impl<const L: u8> Hash for Chord<L> {
|
|||
}
|
||||
|
||||
impl<const L: u8> Chord<L> {
|
||||
pub fn as_erased<const M: u8>(self: &Arc<Self>) -> &Arc<Chord<M>> {
|
||||
unsafe { &*(self as *const Arc<Chord<L>> as *const Arc<Chord<M>>) }
|
||||
}
|
||||
|
||||
pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() }
|
||||
|
||||
pub fn run(&self) -> String {
|
||||
|
|
@ -117,7 +114,7 @@ impl ChordMatcher {
|
|||
// --- Iter
|
||||
#[derive(Default)]
|
||||
pub struct ChordIter {
|
||||
pub chords: Arc<Vec<Arc<Chord>>>,
|
||||
pub chords: Arc<Vec<ChordArc>>,
|
||||
pub matcher: ChordMatcher,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
|
@ -133,7 +130,7 @@ impl From<&Chords> for ChordIter {
|
|||
}
|
||||
|
||||
impl Iterator for ChordIter {
|
||||
type Item = Arc<Chord>;
|
||||
type Item = ChordArc;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(chord) = self.chords.get(self.offset) {
|
||||
|
|
|
|||
66
yazi-config/src/keymap/chord_arc.rs
Normal file
66
yazi-config/src/keymap/chord_arc.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use std::{ops::{Deref, DerefMut}, sync::Arc};
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{Layer, event::ActionCow};
|
||||
|
||||
use crate::{Mixable, keymap::Chord};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[repr(transparent)]
|
||||
pub struct ChordArc<const L: u8 = { Layer::Null as u8 }>(Arc<Chord<L>>);
|
||||
|
||||
impl<const L: u8> Deref for ChordArc<L> {
|
||||
type Target = Arc<Chord<L>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> DerefMut for ChordArc<L> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&ChordArc<L>> for ChordArc<L> {
|
||||
fn from(value: &ChordArc<L>) -> Self { value.clone() }
|
||||
}
|
||||
|
||||
impl<const L: u8, const M: u8> From<Chord<L>> for ChordArc<M> {
|
||||
fn from(value: Chord<L>) -> Self { ChordArc(Arc::new(value)).into_erased() }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<ChordArc<L>> for Chord<L> {
|
||||
fn from(value: ChordArc<L>) -> Self {
|
||||
match Arc::try_unwrap(value.0) {
|
||||
Ok(c) => c,
|
||||
Err(arc) => Self::clone(&arc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&ChordArc<L>> for Chord<L> {
|
||||
fn from(value: &ChordArc<L>) -> Self { Self::clone(value) }
|
||||
}
|
||||
|
||||
impl<const L: u8> ChordArc<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> &ChordArc<M> {
|
||||
unsafe { &*(self as *const ChordArc<L> as *const ChordArc<M>) }
|
||||
}
|
||||
|
||||
pub fn into_erased<const M: u8>(self) -> ChordArc<M> {
|
||||
ChordArc(unsafe { Arc::from_raw(Arc::into_raw(self.0) as *const Chord<M>) })
|
||||
}
|
||||
|
||||
pub fn to_seq(&self) -> Vec<ActionCow> {
|
||||
self.run.iter().rev().cloned().map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn into_seq(self) -> Vec<ActionCow> {
|
||||
match Arc::try_unwrap(self.0) {
|
||||
Ok(c) => c.run.into_iter().rev().map(Into::into).collect(),
|
||||
Err(arc) => Self(arc).to_seq(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Mixable for ChordArc<L> {
|
||||
fn filter(&self) -> bool { self.0.filter() }
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use yazi_shared::{Layer, event::ActionCow};
|
||||
|
||||
use super::Chord;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ChordCow<const L: u8 = { Layer::Null as u8 }> {
|
||||
Owned(Chord<L>),
|
||||
Borrowed(Arc<Chord<L>>),
|
||||
}
|
||||
|
||||
impl<const L: u8> From<Chord<L>> for ChordCow<L> {
|
||||
fn from(c: Chord<L>) -> Self { Self::Owned(c) }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<Arc<Chord<L>>> for ChordCow<L> {
|
||||
fn from(c: Arc<Chord<L>>) -> Self { Self::Borrowed(c) }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<&Arc<Chord<L>>> for ChordCow<L> {
|
||||
fn from(c: &Arc<Chord<L>>) -> Self { Self::Borrowed(c.clone()) }
|
||||
}
|
||||
|
||||
impl<const L: u8> Deref for ChordCow<L> {
|
||||
type Target = Chord<L>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Owned(c) => c,
|
||||
Self::Borrowed(c) => c,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: u8> Default for ChordCow<L> {
|
||||
fn default() -> Self { Self::Owned(Chord::default()) }
|
||||
}
|
||||
|
||||
impl<const L: u8> ChordCow<L> {
|
||||
pub fn into_seq(self) -> Vec<ActionCow> {
|
||||
match self {
|
||||
Self::Owned(c) => c.run.into_iter().rev().map(Into::into).collect(),
|
||||
Self::Borrowed(c) => c.run.iter().rev().cloned().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,29 +5,28 @@ use serde::Deserialize;
|
|||
use yazi_shared::Layer;
|
||||
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, vec::{IndexAtError, VecExt}};
|
||||
|
||||
use super::Chord;
|
||||
use crate::keymap::ChordMatcher;
|
||||
use crate::keymap::{Chord, ChordArc, ChordMatcher};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Chords<const L: u8 = { Layer::Null as u8 }>(ArcSwap<Vec<Arc<Chord<L>>>>);
|
||||
pub struct Chords<const L: u8 = { Layer::Null as u8 }>(ArcSwap<Vec<ChordArc<L>>>);
|
||||
|
||||
impl<const L: u8> Deref for Chords<L> {
|
||||
type Target = ArcSwap<Vec<Arc<Chord<L>>>>;
|
||||
type Target = ArcSwap<Vec<ChordArc<L>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<const L: u8> From<Vec<Arc<Chord<L>>>> for Chords<L> {
|
||||
fn from(inner: Vec<Arc<Chord<L>>>) -> Self { Self(inner.into_pointee()) }
|
||||
impl<const L: u8> From<Vec<ChordArc<L>>> for Chords<L> {
|
||||
fn from(inner: Vec<ChordArc<L>>) -> Self { Self(inner.into_pointee()) }
|
||||
}
|
||||
|
||||
impl<const L: u8> Chords<L> {
|
||||
pub fn as_erased<const M: u8>(&self) -> Arc<Vec<Arc<Chord<M>>>> {
|
||||
pub fn as_erased<const M: u8>(&self) -> Arc<Vec<ChordArc<M>>> {
|
||||
let chords = self.0.load_full();
|
||||
unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec<Arc<Chord<M>>>) }
|
||||
unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec<ChordArc<M>>) }
|
||||
}
|
||||
|
||||
pub fn insert(&self, index: isize, rule: Arc<Chord>) -> Result<(), IndexAtError> {
|
||||
pub fn insert(&self, index: isize, rule: ChordArc) -> Result<(), IndexAtError> {
|
||||
self.0.try_rcu(|rules| {
|
||||
let (before, after) = rules.split_at(rules.index_at(index)?);
|
||||
Ok(
|
||||
|
|
@ -46,12 +45,30 @@ impl<const L: u8> Chords<L> {
|
|||
pub fn remove(&self, matcher: ChordMatcher) {
|
||||
self.0.rcu(|chords| {
|
||||
let mut next = Vec::clone(chords);
|
||||
next.retain(|chord| !matcher.matches(chord.as_erased()));
|
||||
next.retain(|arc| !matcher.matches(arc.as_erased()));
|
||||
next
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<Arc<Chord<L>>> {
|
||||
pub fn update<E>(
|
||||
&self,
|
||||
matcher: ChordMatcher,
|
||||
f: impl Fn(Chord) -> Result<Chord, E>,
|
||||
) -> Result<(), E> {
|
||||
self.0.try_rcu(|rules| {
|
||||
let mut next = Vec::clone(rules);
|
||||
for arc in &mut next {
|
||||
if matcher.matches(arc.as_erased()) {
|
||||
*arc = f(Chord::clone(arc.as_erased()))?.into();
|
||||
}
|
||||
}
|
||||
Ok(Arc::new(next))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<ChordArc<L>> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique chords arc")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use yazi_codegen::{DeserializeOver, DeserializeOver1};
|
|||
use yazi_fs::{Xdg, ok_or_not_found};
|
||||
use yazi_shared::Layer;
|
||||
|
||||
use super::{Chord, KeymapSection};
|
||||
use super::KeymapSection;
|
||||
use crate::keymap::ChordArc;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1)]
|
||||
pub struct Keymap {
|
||||
|
|
@ -21,7 +22,7 @@ pub struct Keymap {
|
|||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn get(&self, layer: Layer) -> Arc<Vec<Arc<Chord>>> {
|
||||
pub fn get(&self, layer: Layer) -> Arc<Vec<ChordArc>> {
|
||||
match layer {
|
||||
Layer::Null | Layer::App => Arc::new(Vec::new()),
|
||||
Layer::Mgr => self.mgr.deref().as_erased(),
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(chord chord_cow chords ids key keymap section);
|
||||
yazi_macro::mod_flat!(chord chord_arc chords ids key keymap section);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
use std::ops::Deref;
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -7,7 +7,7 @@ use yazi_shared::Layer;
|
|||
use yazi_shim::toml::DeserializeOverHook;
|
||||
|
||||
use super::{Chord, Chords, Key};
|
||||
use crate::mix;
|
||||
use crate::{keymap::ChordArc, mix};
|
||||
|
||||
#[derive(Default, Deserialize, DeserializeOver2)]
|
||||
pub struct KeymapSection<const L: u8 = { Layer::Null as u8 }> {
|
||||
|
|
@ -41,7 +41,7 @@ impl<const L: u8> DeserializeOverHook for KeymapSection<L> {
|
|||
let a_seen: HashSet<_> = self.prepend_keymap.iter().map(on).collect();
|
||||
let b_seen: HashSet<_> = keymap.iter().map(|c| on(c)).collect();
|
||||
|
||||
let keymap: Vec<Arc<Chord<L>>> = mix(
|
||||
let keymap: Vec<ChordArc<L>> = mix(
|
||||
self.prepend_keymap,
|
||||
keymap.into_iter().filter(|v| !a_seen.contains(&on(v))),
|
||||
self.append_keymap.into_iter().filter(|v| !b_seen.contains(&on(v))),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
|
||||
use yazi_config::{KEYMAP, YAZI, keymap::{ChordArc, Key}};
|
||||
use yazi_macro::{act, render, render_and};
|
||||
use yazi_shared::Layer;
|
||||
use yazi_term::{CursorStyle, TERM, event::KeyCode};
|
||||
|
|
@ -14,7 +12,7 @@ use crate::help::HELP_MARGIN;
|
|||
pub struct Help {
|
||||
pub visible: bool,
|
||||
pub layer: Layer,
|
||||
pub(super) bindings: Vec<Arc<Chord>>,
|
||||
pub(super) bindings: Vec<ChordArc>,
|
||||
|
||||
// Filter
|
||||
pub keyword: String,
|
||||
|
|
@ -75,7 +73,7 @@ impl Help {
|
|||
}
|
||||
|
||||
// --- Bindings
|
||||
pub fn window(&self) -> &[Arc<Chord>] {
|
||||
pub fn window(&self) -> &[ChordArc] {
|
||||
let end = (self.offset + self.limit()).min(self.bindings.len());
|
||||
&self.bindings[self.offset..end]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_config::{KEYMAP, keymap::{ChordCow, Key}};
|
||||
use yazi_config::{KEYMAP, keymap::{ChordArc, Key}};
|
||||
use yazi_macro::impl_data_any;
|
||||
use yazi_shared::{Layer, event::ActionCow};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WhichOpt {
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<yazi_binding::keymap::ChordCow>>>,
|
||||
pub cands: Vec<ChordCow>,
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<yazi_binding::keymap::Chord>>>,
|
||||
pub cands: Vec<ChordArc>,
|
||||
pub silent: bool,
|
||||
pub times: usize,
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ impl TryFrom<ActionCow> for WhichOpt {
|
|||
|
||||
Ok(Self {
|
||||
tx: a.take_any2("tx").transpose()?,
|
||||
cands: a.take_any_iter::<yazi_binding::keymap::ChordCow>().map(Into::into).collect(),
|
||||
cands: a.take_any_iter::<yazi_binding::keymap::Chord>().map(Into::into).collect(),
|
||||
silent: a.bool("silent"),
|
||||
times: a.get("times").unwrap_or(0),
|
||||
})
|
||||
|
|
@ -39,7 +39,7 @@ impl From<(Layer, Key)> for WhichOpt {
|
|||
.get(layer)
|
||||
.iter()
|
||||
.filter(|&c| c.on.len() > 1 && c.on[0] == key)
|
||||
.map(Into::into)
|
||||
.cloned()
|
||||
.collect(),
|
||||
times: 1,
|
||||
silent: false,
|
||||
|
|
@ -57,7 +57,7 @@ impl FromLua for WhichOpt {
|
|||
tx: t.raw_get::<yazi_binding::MpscUnboundedTx<_>>("tx").ok().map(|t| t.0),
|
||||
cands: t
|
||||
.raw_get::<Table>("cands")?
|
||||
.sequence_values::<yazi_binding::keymap::ChordCow>()
|
||||
.sequence_values::<yazi_binding::keymap::Chord>()
|
||||
.map(|c| c.map(Into::into))
|
||||
.collect::<mlua::Result<Vec<_>>>()?,
|
||||
times: t.raw_get("times").unwrap_or_default(),
|
||||
|
|
@ -72,7 +72,7 @@ impl IntoLua for WhichOpt {
|
|||
lua
|
||||
.create_table_from([
|
||||
("tx", self.tx.map(yazi_binding::MpscUnboundedTx).into_lua(lua)?),
|
||||
("cands", lua.create_sequence_from(self.cands.into_iter().map(yazi_binding::keymap::ChordCow))?.into_lua(lua)?),
|
||||
("cands", lua.create_sequence_from(self.cands.into_iter().map(yazi_binding::keymap::Chord::new))?.into_lua(lua)?),
|
||||
("times", self.times.into_lua(lua)?),
|
||||
("silent", self.silent.into_lua(lua)?),
|
||||
])?
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{borrow::Cow, mem};
|
||||
|
||||
use yazi_config::{YAZI, keymap::ChordCow, which::SortBy};
|
||||
use yazi_config::{YAZI, keymap::ChordArc, which::SortBy};
|
||||
use yazi_shared::{natsort, translit::Transliterator};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
|
|
@ -23,7 +23,7 @@ impl Default for WhichSorter {
|
|||
}
|
||||
|
||||
impl WhichSorter {
|
||||
pub fn sort(&self, items: &mut Vec<ChordCow>) {
|
||||
pub fn sort(&self, items: &mut Vec<ChordArc>) {
|
||||
if self.by == SortBy::None || items.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@ impl WhichSorter {
|
|||
if self.reverse { ordering.reverse() } else { ordering }
|
||||
});
|
||||
|
||||
// TODO: remove the `mem::take` since that involves Arc atomics refcount changes
|
||||
*items = indices.into_iter().map(|i| mem::take(&mut items[i])).collect();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_config::keymap::{ChordCow, Key};
|
||||
use yazi_config::keymap::{ChordArc, Key};
|
||||
use yazi_macro::{emit, render_and};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Which {
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<yazi_binding::keymap::ChordCow>>>,
|
||||
pub tx: Option<mpsc::UnboundedSender<Option<yazi_binding::keymap::Chord>>>,
|
||||
pub times: usize,
|
||||
pub cands: Vec<ChordCow>,
|
||||
pub cands: Vec<ChordArc>,
|
||||
|
||||
// Active state
|
||||
pub active: bool,
|
||||
|
|
@ -31,7 +31,7 @@ impl Which {
|
|||
render_and!(true)
|
||||
}
|
||||
|
||||
pub fn dismiss(&mut self, chord: Option<ChordCow>) {
|
||||
pub fn dismiss(&mut self, chord: Option<ChordArc>) {
|
||||
self.times = 0;
|
||||
self.cands.clear();
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ impl Which {
|
|||
self.silent = false;
|
||||
|
||||
if let Some(tx) = self.tx.take() {
|
||||
_ = tx.send(chord.clone().map(Into::into));
|
||||
_ = tx.send(chord.as_ref().map(Into::into));
|
||||
}
|
||||
if let Some(chord) = chord {
|
||||
emit!(Seq(chord.into_seq()));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_config::{KEYMAP, keymap::{Chord, ChordCow, Key}};
|
||||
use yazi_config::{KEYMAP, keymap::{Chord, Key}};
|
||||
use yazi_macro::act;
|
||||
use yazi_shared::Layer;
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ impl<'a> Router<'a> {
|
|||
|
||||
fn matches(&mut self, layer: Layer, key: Key) -> bool {
|
||||
for chord in &*KEYMAP.get(layer) {
|
||||
let Chord { on, .. } = &**chord;
|
||||
let Chord { on, .. } = chord.as_ref();
|
||||
if on.is_empty() || on[0] != key {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ impl<'a> Router<'a> {
|
|||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(which:activate, cx, (layer, key)).ok();
|
||||
} else {
|
||||
Dispatcher::new(self.app).dispatch_seq(ChordCow::from(chord).into_seq());
|
||||
Dispatcher::new(self.app).dispatch_seq(chord.to_seq());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{str::FromStr, time::Duration};
|
|||
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, Table, Value};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use yazi_binding::{InputRx, elements::{Line, Pos, Text}, runtime};
|
||||
use yazi_config::{Platform, keymap::{Chord, ChordCow, Key}, popup::{ConfirmCfg, InputCfg}};
|
||||
use yazi_config::{Platform, keymap::{Chord, ChordArc, Key}, popup::{ConfirmCfg, InputCfg}};
|
||||
use yazi_core::notify::MessageOpt;
|
||||
use yazi_macro::relay;
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy};
|
||||
|
|
@ -24,7 +24,7 @@ impl Utils {
|
|||
.enumerate()
|
||||
.map(|(i, cand)| {
|
||||
let cand = cand?;
|
||||
Ok(ChordCow::Owned(Chord {
|
||||
Ok(ChordArc::from(Chord::<{ Layer::Null as u8 }> {
|
||||
id: yazi_config::keymap::chord_id(),
|
||||
on: Self::parse_keys(cand.raw_get("on")?)?,
|
||||
run: relay!(which:callback, [i + 1]).into(),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_config::keymap::ChordCow;
|
||||
use yazi_config::keymap::ChordArc;
|
||||
use yazi_core::which::WhichOpt;
|
||||
use yazi_macro::{emit, relay};
|
||||
|
||||
pub struct WhichProxy;
|
||||
|
||||
impl WhichProxy {
|
||||
pub async fn activate(cands: Vec<ChordCow>, silent: bool) -> Option<ChordCow> {
|
||||
pub async fn activate(cands: Vec<ChordArc>, silent: bool) -> Option<ChordArc> {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
emit!(Call(relay!(which:activate).with_any("opt", WhichOpt {
|
||||
tx: Some(tx),
|
||||
|
|
@ -14,6 +14,6 @@ impl WhichProxy {
|
|||
silent,
|
||||
times: 0,
|
||||
})));
|
||||
Some(rx.recv().await??.0)
|
||||
Some(rx.recv().await??.into())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,21 @@ impl From<Action> for Actions {
|
|||
fn from(value: Action) -> Self { Self(vec![value]) }
|
||||
}
|
||||
|
||||
impl From<Vec<Action>> for Actions {
|
||||
fn from(value: Vec<Action>) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl Actions {
|
||||
pub fn set(&mut self, layer: Layer, source: Source) {
|
||||
for action in &mut self.0 {
|
||||
action.source = source;
|
||||
if action.layer == Layer::Null {
|
||||
action.layer = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Actions {
|
||||
type IntoIter = vec::IntoIter<Action>;
|
||||
type Item = Action;
|
||||
|
|
@ -44,14 +59,8 @@ where
|
|||
return Err(de::Error::custom(format!("invalid keymap layer const: {L}")));
|
||||
};
|
||||
|
||||
let mut actions: Vec<Action> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?;
|
||||
let mut actions = Actions(OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?);
|
||||
actions.set(layer, Source::Key);
|
||||
|
||||
for action in &mut actions {
|
||||
action.source = Source::Key;
|
||||
if action.layer == Layer::Null {
|
||||
action.layer = layer;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Actions(actions))
|
||||
Ok(actions)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue