diff --git a/CHANGELOG.md b/CHANGELOG.md index ea999c49..ce4e5940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21c1e5c9..ce91de1d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 9f54b156..0f36b1b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5674,6 +5674,7 @@ dependencies = [ "ordered-float 5.3.0", "paste", "ratatui", + "serde", "serde_json", "tokio", "tokio-stream", diff --git a/yazi-actor/src/lives/which.rs b/yazi-actor/src/lives/which.rs index 8046728a..eaa90e43 100644 --- a/yazi-actor/src/lives/which.rs +++ b/yazi-actor/src/lives/which.rs @@ -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)); diff --git a/yazi-binding/Cargo.toml b/yazi-binding/Cargo.toml index 9a27f52e..52506079 100644 --- a/yazi-binding/Cargo.toml +++ b/yazi-binding/Cargo.toml @@ -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 } diff --git a/yazi-binding/src/event/action.rs b/yazi-binding/src/event/action.rs index d100e10a..984429c6 100644 --- a/yazi-binding/src/event/action.rs +++ b/yazi-binding/src/event/action.rs @@ -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 for yazi_shared::event::Action { + fn from(value: Action) -> Self { value.inner } +} + +impl From for yazi_shared::event::Actions { + fn from(value: Action) -> Self { Self::from(value.inner) } +} + impl Action { pub fn new(inner: impl Into) -> Self { Self { inner: inner.into() } } } -impl UserData for Action { - fn add_fields>(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 { + 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>(fields: &mut F) { + fields.add_cached_field("cmd", |_, me| Ok(Cmd::new(me.cmd.clone()))); } } diff --git a/yazi-binding/src/event/actions.rs b/yazi-binding/src/event/actions.rs new file mode 100644 index 00000000..2cd1a942 --- /dev/null +++ b/yazi-binding/src/event/actions.rs @@ -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 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 { + let inner = match value { + Value::Table(t) => t + .sequence_values::() + .map(|a| a.map(Into::into)) + .collect::>>()? + .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 { + lua.create_sequence_from(self.inner.into_iter().map(Action::new))?.into_lua(lua) + } +} diff --git a/yazi-binding/src/event/mod.rs b/yazi-binding/src/event/mod.rs index 62b86ac6..c0011e6d 100644 --- a/yazi-binding/src/event/mod.rs +++ b/yazi-binding/src/event/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(action cmd); +yazi_macro::mod_flat!(action actions cmd); diff --git a/yazi-binding/src/keymap/chord.rs b/yazi-binding/src/keymap/chord.rs index 057c6015..9c417349 100644 --- a/yazi-binding/src/keymap/chord.rs +++ b/yazi-binding/src/keymap/chord.rs @@ -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, + inner: yazi_config::keymap::ChordArc, } impl Deref for Chord { - type Target = Arc; + type Target = yazi_config::keymap::ChordArc; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for Arc { +impl From<&yazi_config::keymap::ChordArc> for Chord { + fn from(value: &yazi_config::keymap::ChordArc) -> Self { Self { inner: value.clone() } } +} + +impl From for yazi_config::keymap::ChordArc { fn from(value: Chord) -> Self { value.inner } } impl Chord { - pub fn new(inner: impl Into>) -> Self { + pub fn new(inner: impl Into) -> Self { Self { inner: inner.into() } } } -impl FromLua for Chord { - fn from_lua(value: Value, lua: &Lua) -> mlua::Result { - Ok(Self::new(lua.from_value::(value)?)) +impl TryFrom<(Value, yazi_shared::Layer)> for Chord { + type Error = mlua::Error; + + fn try_from((value, layer): (Value, yazi_shared::Layer)) -> Result { + 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)); } diff --git a/yazi-binding/src/keymap/chord_cow.rs b/yazi-binding/src/keymap/chord_cow.rs deleted file mode 100644 index dc5821db..00000000 --- a/yazi-binding/src/keymap/chord_cow.rs +++ /dev/null @@ -1,15 +0,0 @@ -use mlua::UserData; -use yazi_codegen::FromLuaOwned; - -#[derive(Clone, FromLuaOwned)] -pub struct ChordCow(pub yazi_config::keymap::ChordCow); - -impl From for ChordCow { - fn from(value: yazi_config::keymap::ChordCow) -> Self { Self(value) } -} - -impl From for yazi_config::keymap::ChordCow { - fn from(value: ChordCow) -> Self { value.0 } -} - -impl UserData for ChordCow {} diff --git a/yazi-binding/src/keymap/chords.rs b/yazi-binding/src/keymap/chords.rs index 07c160bc..2b1a3f6a 100644 --- a/yazi-binding/src/keymap/chords.rs +++ b/yazi-binding/src/keymap/chords.rs @@ -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>(methods: &mut M) { methods.add_method("match", |_, me, matcher: Option| { @@ -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 = 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())); } } diff --git a/yazi-binding/src/keymap/mod.rs b/yazi-binding/src/keymap/mod.rs index 80857a4a..b8f2ac0e 100644 --- a/yazi-binding/src/keymap/mod.rs +++ b/yazi-binding/src/keymap/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(chord chord_cow chords key section); +yazi_macro::mod_flat!(chord chords key section); diff --git a/yazi-binding/src/keymap/section.rs b/yazi-binding/src/keymap/section.rs index 99894e0c..48fd44a9 100644 --- a/yazi-binding/src/keymap/section.rs +++ b/yazi-binding/src/keymap/section.rs @@ -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 for KeymapSection { +impl TryFrom for KeymapSection { type Error = anyhow::Error; - fn try_from(value: Layer) -> Result { - 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 { + 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>(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 })); } } diff --git a/yazi-config/src/keymap/chord.rs b/yazi-config/src/keymap/chord.rs index 98aba87e..587a63c4 100644 --- a/yazi-config/src/keymap/chord.rs +++ b/yazi-config/src/keymap/chord.rs @@ -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 = OnceLock::new(); -#[derive(Debug, Default, Deserialize)] +#[derive(Debug, Default, Deserialize, DeserializeOver2)] pub struct Chord { #[serde(skip, default = "chord_id")] pub id: Id, @@ -47,10 +48,6 @@ impl Hash for Chord { } impl Chord { - pub fn as_erased(self: &Arc) -> &Arc> { - unsafe { &*(self as *const Arc> as *const Arc>) } - } - 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>>, + pub chords: Arc>, pub matcher: ChordMatcher, pub offset: usize, } @@ -133,7 +130,7 @@ impl From<&Chords> for ChordIter { } impl Iterator for ChordIter { - type Item = Arc; + type Item = ChordArc; fn next(&mut self) -> Option { while let Some(chord) = self.chords.get(self.offset) { diff --git a/yazi-config/src/keymap/chord_arc.rs b/yazi-config/src/keymap/chord_arc.rs new file mode 100644 index 00000000..c1fe3fee --- /dev/null +++ b/yazi-config/src/keymap/chord_arc.rs @@ -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(Arc>); + +impl Deref for ChordArc { + type Target = Arc>; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +impl DerefMut for ChordArc { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +} + +impl From<&ChordArc> for ChordArc { + fn from(value: &ChordArc) -> Self { value.clone() } +} + +impl From> for ChordArc { + fn from(value: Chord) -> Self { ChordArc(Arc::new(value)).into_erased() } +} + +impl From> for Chord { + fn from(value: ChordArc) -> Self { + match Arc::try_unwrap(value.0) { + Ok(c) => c, + Err(arc) => Self::clone(&arc), + } + } +} + +impl From<&ChordArc> for Chord { + fn from(value: &ChordArc) -> Self { Self::clone(value) } +} + +impl ChordArc { + pub fn as_erased(&self) -> &ChordArc { + unsafe { &*(self as *const ChordArc as *const ChordArc) } + } + + pub fn into_erased(self) -> ChordArc { + ChordArc(unsafe { Arc::from_raw(Arc::into_raw(self.0) as *const Chord) }) + } + + pub fn to_seq(&self) -> Vec { + self.run.iter().rev().cloned().map(Into::into).collect() + } + + pub fn into_seq(self) -> Vec { + match Arc::try_unwrap(self.0) { + Ok(c) => c.run.into_iter().rev().map(Into::into).collect(), + Err(arc) => Self(arc).to_seq(), + } + } +} + +impl Mixable for ChordArc { + fn filter(&self) -> bool { self.0.filter() } +} diff --git a/yazi-config/src/keymap/chord_cow.rs b/yazi-config/src/keymap/chord_cow.rs deleted file mode 100644 index cf9a2d1c..00000000 --- a/yazi-config/src/keymap/chord_cow.rs +++ /dev/null @@ -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 { - Owned(Chord), - Borrowed(Arc>), -} - -impl From> for ChordCow { - fn from(c: Chord) -> Self { Self::Owned(c) } -} - -impl From>> for ChordCow { - fn from(c: Arc>) -> Self { Self::Borrowed(c) } -} - -impl From<&Arc>> for ChordCow { - fn from(c: &Arc>) -> Self { Self::Borrowed(c.clone()) } -} - -impl Deref for ChordCow { - type Target = Chord; - - fn deref(&self) -> &Self::Target { - match self { - Self::Owned(c) => c, - Self::Borrowed(c) => c, - } - } -} - -impl Default for ChordCow { - fn default() -> Self { Self::Owned(Chord::default()) } -} - -impl ChordCow { - pub fn into_seq(self) -> Vec { - 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(), - } - } -} diff --git a/yazi-config/src/keymap/chords.rs b/yazi-config/src/keymap/chords.rs index 7f6f0807..06a35f49 100644 --- a/yazi-config/src/keymap/chords.rs +++ b/yazi-config/src/keymap/chords.rs @@ -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(ArcSwap>>>); +pub struct Chords(ArcSwap>>); impl Deref for Chords { - type Target = ArcSwap>>>; + type Target = ArcSwap>>; fn deref(&self) -> &Self::Target { &self.0 } } -impl From>>> for Chords { - fn from(inner: Vec>>) -> Self { Self(inner.into_pointee()) } +impl From>> for Chords { + fn from(inner: Vec>) -> Self { Self(inner.into_pointee()) } } impl Chords { - pub fn as_erased(&self) -> Arc>>> { + pub fn as_erased(&self) -> Arc>> { let chords = self.0.load_full(); - unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec>>) } + unsafe { Arc::from_raw(Arc::into_raw(chords) as *const Vec>) } } - pub fn insert(&self, index: isize, rule: Arc) -> 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 Chords { 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>> { + pub fn update( + &self, + matcher: ChordMatcher, + f: impl Fn(Chord) -> Result, + ) -> 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> { Arc::try_unwrap(self.0.into_inner()).expect("unique chords arc") } } diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index 36712efe..1ee131d8 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -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>> { + pub fn get(&self, layer: Layer) -> Arc> { match layer { Layer::Null | Layer::App => Arc::new(Vec::new()), Layer::Mgr => self.mgr.deref().as_erased(), diff --git a/yazi-config/src/keymap/mod.rs b/yazi-config/src/keymap/mod.rs index bd0d6786..eda8e777 100644 --- a/yazi-config/src/keymap/mod.rs +++ b/yazi-config/src/keymap/mod.rs @@ -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); diff --git a/yazi-config/src/keymap/section.rs b/yazi-config/src/keymap/section.rs index 42df0036..a309d164 100644 --- a/yazi-config/src/keymap/section.rs +++ b/yazi-config/src/keymap/section.rs @@ -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 { @@ -41,7 +41,7 @@ impl DeserializeOverHook for KeymapSection { 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>> = mix( + let keymap: Vec> = 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))), diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index a59aa23d..35c1abd8 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -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>, + pub(super) bindings: Vec, // Filter pub keyword: String, @@ -75,7 +73,7 @@ impl Help { } // --- Bindings - pub fn window(&self) -> &[Arc] { + pub fn window(&self) -> &[ChordArc] { let end = (self.offset + self.limit()).min(self.bindings.len()); &self.bindings[self.offset..end] } diff --git a/yazi-core/src/which/option.rs b/yazi-core/src/which/option.rs index b6a5c53a..db3a917c 100644 --- a/yazi-core/src/which/option.rs +++ b/yazi-core/src/which/option.rs @@ -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>>, - pub cands: Vec, + pub tx: Option>>, + pub cands: Vec, pub silent: bool, pub times: usize, } @@ -24,7 +24,7 @@ impl TryFrom for WhichOpt { Ok(Self { tx: a.take_any2("tx").transpose()?, - cands: a.take_any_iter::().map(Into::into).collect(), + cands: a.take_any_iter::().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::>("tx").ok().map(|t| t.0), cands: t .raw_get::("cands")? - .sequence_values::() + .sequence_values::() .map(|c| c.map(Into::into)) .collect::>>()?, 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)?), ])? diff --git a/yazi-core/src/which/sorter.rs b/yazi-core/src/which/sorter.rs index a74c9215..6b5f41ac 100644 --- a/yazi-core/src/which/sorter.rs +++ b/yazi-core/src/which/sorter.rs @@ -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) { + pub fn sort(&self, items: &mut Vec) { 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(); } } diff --git a/yazi-core/src/which/which.rs b/yazi-core/src/which/which.rs index 6b772761..9392d89a 100644 --- a/yazi-core/src/which/which.rs +++ b/yazi-core/src/which/which.rs @@ -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>>, + pub tx: Option>>, pub times: usize, - pub cands: Vec, + pub cands: Vec, // Active state pub active: bool, @@ -31,7 +31,7 @@ impl Which { render_and!(true) } - pub fn dismiss(&mut self, chord: Option) { + pub fn dismiss(&mut self, chord: Option) { 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())); diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index 5b0b0d48..151fa709 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -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; } diff --git a/yazi-plugin/src/utils/layer.rs b/yazi-plugin/src/utils/layer.rs index 586ffedf..80c4b9f4 100644 --- a/yazi-plugin/src/utils/layer.rs +++ b/yazi-plugin/src/utils/layer.rs @@ -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(), diff --git a/yazi-proxy/src/which.rs b/yazi-proxy/src/which.rs index 3d52d8ab..1d1fa095 100644 --- a/yazi-proxy/src/which.rs +++ b/yazi-proxy/src/which.rs @@ -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, silent: bool) -> Option { + pub async fn activate(cands: Vec, silent: bool) -> Option { 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()) } } diff --git a/yazi-shared/src/event/actions.rs b/yazi-shared/src/event/actions.rs index 96892cc0..f0f6f937 100644 --- a/yazi-shared/src/event/actions.rs +++ b/yazi-shared/src/event/actions.rs @@ -22,6 +22,21 @@ impl From for Actions { fn from(value: Action) -> Self { Self(vec![value]) } } +impl From> for Actions { + fn from(value: Vec) -> 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; type Item = Action; @@ -44,14 +59,8 @@ where return Err(de::Error::custom(format!("invalid keymap layer const: {L}"))); }; - let mut actions: Vec = OneOrMany::::deserialize_as(deserializer)?; + let mut actions = Actions(OneOrMany::::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) }