diff --git a/Cargo.lock b/Cargo.lock index c21be335..c1dfa5e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -406,9 +406,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.23" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff" +checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" dependencies = [ "clap", ] @@ -1144,6 +1144,7 @@ checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 8b8e03b4..99eb1d93 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -20,7 +20,7 @@ serde = { workspace = true } [build-dependencies] clap = { workspace = true } -clap_complete = "4.5.23" +clap_complete = "4.5.24" clap_complete_fig = "4.5.2" clap_complete_nushell = "4.5.3" vergen-gitcl = { version = "1.0.0", features = [ "build" ] } diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 114f2784..c4cd7f3a 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -28,7 +28,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.3.1" } # External build dependencies anyhow = { workspace = true } clap = { workspace = true } -clap_complete = "4.5.23" +clap_complete = "4.5.24" clap_complete_fig = "4.5.2" clap_complete_nushell = "4.5.3" serde_json = { workspace = true } diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index 76864f74..06d8a3c1 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -17,7 +17,7 @@ arc-swap = { workspace = true } bitflags = { workspace = true } crossterm = { workspace = true } globset = { workspace = true } -indexmap = "2.4.0" +indexmap = { version = "2.4.0", features = [ "serde" ] } ratatui = { workspace = true } regex = { workspace = true } serde = { workspace = true } diff --git a/yazi-config/src/keymap/control.rs b/yazi-config/src/keymap/chord.rs similarity index 79% rename from yazi-config/src/keymap/control.rs rename to yazi-config/src/keymap/chord.rs index 456cc7fa..7cfa995d 100644 --- a/yazi-config/src/keymap/control.rs +++ b/yazi-config/src/keymap/chord.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::VecDeque, sync::OnceLock}; +use std::{borrow::Cow, collections::VecDeque, hash::{Hash, Hasher}, sync::OnceLock}; use regex::Regex; use serde::Deserialize; @@ -9,7 +9,7 @@ use super::Key; static RE: OnceLock = OnceLock::new(); #[derive(Debug, Default, Deserialize)] -pub struct Control { +pub struct Chord { #[serde(deserialize_with = "super::deserialize_on")] pub on: Vec, #[serde(deserialize_with = "super::deserialize_run")] @@ -17,12 +17,17 @@ pub struct Control { pub desc: Option, } -impl Control { - #[inline] - pub fn to_seq(&self) -> VecDeque { self.run.iter().map(|c| c.shallow_clone()).collect() } +impl PartialEq for Chord { + fn eq(&self, other: &Self) -> bool { self.on == other.on } } -impl Control { +impl Eq for Chord {} + +impl Hash for Chord { + fn hash(&self, state: &mut H) { self.on.hash(state) } +} + +impl Chord { pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() } pub fn run(&self) -> String { @@ -44,4 +49,7 @@ impl Control { || self.run().to_lowercase().contains(&s) || self.on().to_lowercase().contains(&s) } + + #[inline] + pub fn to_seq(&self) -> VecDeque { self.run.iter().map(|c| c.shallow_clone()).collect() } } diff --git a/yazi-config/src/keymap/cow.rs b/yazi-config/src/keymap/cow.rs index 01d71105..cc671d82 100644 --- a/yazi-config/src/keymap/cow.rs +++ b/yazi-config/src/keymap/cow.rs @@ -2,24 +2,24 @@ use std::{collections::VecDeque, ops::Deref}; use yazi_shared::event::Cmd; -use super::Control; +use super::Chord; #[derive(Debug)] -pub enum ControlCow { - Owned(Control), - Borrowed(&'static Control), +pub enum ChordCow { + Owned(Chord), + Borrowed(&'static Chord), } -impl From<&'static Control> for ControlCow { - fn from(c: &'static Control) -> Self { Self::Borrowed(c) } +impl From<&'static Chord> for ChordCow { + fn from(c: &'static Chord) -> Self { Self::Borrowed(c) } } -impl From for ControlCow { - fn from(c: Control) -> Self { Self::Owned(c) } +impl From for ChordCow { + fn from(c: Chord) -> Self { Self::Owned(c) } } -impl Deref for ControlCow { - type Target = Control; +impl Deref for ChordCow { + type Target = Chord; fn deref(&self) -> &Self::Target { match self { @@ -29,11 +29,11 @@ impl Deref for ControlCow { } } -impl Default for ControlCow { - fn default() -> Self { Self::Owned(Control::default()) } +impl Default for ChordCow { + fn default() -> Self { Self::Owned(Chord::default()) } } -impl ControlCow { +impl ChordCow { pub fn into_seq(self) -> VecDeque { match self { Self::Owned(c) => c.run.into(), diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index c92ae526..cfdb43ca 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -1,25 +1,26 @@ -use std::str::FromStr; +use std::{collections::HashSet, str::FromStr}; +use indexmap::IndexSet; use serde::{Deserialize, Deserializer}; use yazi_shared::Layer; -use super::Control; +use super::Chord; use crate::Preset; #[derive(Debug)] pub struct Keymap { - pub manager: Vec, - pub tasks: Vec, - pub select: Vec, - pub input: Vec, - pub confirm: Vec, - pub help: Vec, - pub completion: Vec, + pub manager: Vec, + pub tasks: Vec, + pub select: Vec, + pub input: Vec, + pub confirm: Vec, + pub help: Vec, + pub completion: Vec, } impl Keymap { #[inline] - pub fn get(&self, layer: Layer) -> &Vec { + pub fn get(&self, layer: Layer) -> &Vec { match layer { Layer::App => unreachable!(), Layer::Manager => &self.manager, @@ -57,38 +58,38 @@ impl<'de> Deserialize<'de> for Keymap { } #[derive(Deserialize)] struct Inner { - keymap: Vec, + keymap: IndexSet, #[serde(default)] - prepend_keymap: Vec, + prepend_keymap: IndexSet, #[serde(default)] - append_keymap: Vec, + append_keymap: IndexSet, } - let mut shadow = Shadow::deserialize(deserializer)?; + fn mix(mut a: IndexSet, b: IndexSet, c: IndexSet) -> Vec { + let mut seen = HashSet::new(); + b.iter().filter_map(|v| v.on.get(1)).for_each(|&k| _ = seen.insert(k)); + c.iter().filter_map(|v| v.on.get(1)).for_each(|&k| _ = seen.insert(k)); - #[rustfmt::skip] - Preset::mix(&mut shadow.manager.keymap, shadow.manager.prepend_keymap, shadow.manager.append_keymap); - #[rustfmt::skip] - Preset::mix(&mut shadow.tasks.keymap, shadow.tasks.prepend_keymap, shadow.tasks.append_keymap); - #[rustfmt::skip] - Preset::mix(&mut shadow.select.keymap, shadow.select.prepend_keymap, shadow.select.append_keymap); - #[rustfmt::skip] - Preset::mix(&mut shadow.input.keymap, shadow.input.prepend_keymap, shadow.input.append_keymap); - #[rustfmt::skip] - Preset::mix(&mut shadow.confirm.keymap, shadow.confirm.prepend_keymap, shadow.confirm.append_keymap); - #[rustfmt::skip] - Preset::mix(&mut shadow.help.keymap, shadow.help.prepend_keymap, shadow.help.append_keymap); - #[rustfmt::skip] - Preset::mix(&mut shadow.completion.keymap, shadow.completion.prepend_keymap, shadow.completion.append_keymap); + a.retain(|v| v.on.len() < 2 || !seen.contains(&v.on[1])); + Preset::mix(a, b, c).collect() + } + let shadow = Shadow::deserialize(deserializer)?; Ok(Self { - manager: shadow.manager.keymap, - tasks: shadow.tasks.keymap, - select: shadow.select.keymap, - input: shadow.input.keymap, - confirm: shadow.confirm.keymap, - help: shadow.help.keymap, - completion: shadow.completion.keymap, + #[rustfmt::skip] + manager: mix(shadow.manager.keymap, shadow.manager.prepend_keymap, shadow.manager.append_keymap), + #[rustfmt::skip] + tasks: mix(shadow.tasks.keymap, shadow.tasks.prepend_keymap, shadow.tasks.append_keymap), + #[rustfmt::skip] + select: mix(shadow.select.keymap, shadow.select.prepend_keymap, shadow.select.append_keymap), + #[rustfmt::skip] + input: mix(shadow.input.keymap, shadow.input.prepend_keymap, shadow.input.append_keymap), + #[rustfmt::skip] + confirm: mix(shadow.confirm.keymap, shadow.confirm.prepend_keymap, shadow.confirm.append_keymap), + #[rustfmt::skip] + help: mix(shadow.help.keymap, shadow.help.prepend_keymap, shadow.help.append_keymap), + #[rustfmt::skip] + completion: mix(shadow.completion.keymap, shadow.completion.prepend_keymap, shadow.completion.append_keymap), }) } } diff --git a/yazi-config/src/keymap/mod.rs b/yazi-config/src/keymap/mod.rs index 6c330dd3..92fde64b 100644 --- a/yazi-config/src/keymap/mod.rs +++ b/yazi-config/src/keymap/mod.rs @@ -1,10 +1,10 @@ -mod control; +mod chord; mod cow; mod deserializers; mod key; mod keymap; -pub use control::*; +pub use chord::*; pub use cow::*; use deserializers::*; pub use key::*; diff --git a/yazi-config/src/open/open.rs b/yazi-config/src/open/open.rs index a83fbe92..ec44330e 100644 --- a/yazi-config/src/open/open.rs +++ b/yazi-config/src/open/open.rs @@ -87,7 +87,6 @@ impl<'de> Deserialize<'de> for Open { if outer.open.append_rules.iter().any(|r| r.any_dir()) { outer.open.rules.retain(|r| !r.any_dir()); } - Preset::mix(&mut outer.open.rules, outer.open.prepend_rules, outer.open.append_rules); let openers = outer .opener @@ -95,6 +94,10 @@ impl<'de> Deserialize<'de> for Open { .map(|(k, v)| (k, v.into_iter().filter_map(|o| o.take()).collect::>())) .collect(); - Ok(Self { rules: outer.open.rules, openers }) + Ok(Self { + #[rustfmt::skip] + rules: Preset::mix(outer.open.rules, outer.open.prepend_rules, outer.open.append_rules).collect(), + openers, + }) } } diff --git a/yazi-config/src/plugin/plugin.rs b/yazi-config/src/plugin/plugin.rs index 5ab3572c..59c89e99 100644 --- a/yazi-config/src/plugin/plugin.rs +++ b/yazi-config/src/plugin/plugin.rs @@ -86,9 +86,12 @@ impl FromStr for Plugin { shadow.previewers.retain(|r| !r.any_dir()); } - Preset::mix(&mut shadow.fetchers, shadow.prepend_fetchers, shadow.append_fetchers); - Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders); - Preset::mix(&mut shadow.previewers, shadow.prepend_previewers, shadow.append_previewers); + shadow.fetchers = + Preset::mix(shadow.fetchers, shadow.prepend_fetchers, shadow.append_fetchers).collect(); + shadow.preloaders = + Preset::mix(shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders).collect(); + shadow.previewers = + Preset::mix(shadow.previewers, shadow.prepend_previewers, shadow.append_previewers).collect(); if shadow.fetchers.len() + shadow.preloaders.len() > MAX_PREWORKERS as usize { panic!("Fetchers and preloaders exceed the limit of {MAX_PREWORKERS}"); diff --git a/yazi-config/src/preset.rs b/yazi-config/src/preset.rs index 67c59ee3..4f7d4940 100644 --- a/yazi-config/src/preset.rs +++ b/yazi-config/src/preset.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, mem, path::{Path, PathBuf}}; +use std::{borrow::Cow, path::{Path, PathBuf}}; use anyhow::{anyhow, Context, Result}; use toml::{Table, Value}; @@ -32,8 +32,11 @@ impl Preset { } #[inline] - pub(crate) fn mix(a: &mut Vec, b: Vec, c: Vec) { - *a = b.into_iter().chain(mem::take(a)).chain(c).collect(); + pub(crate) fn mix(a: T, b: T, c: T) -> impl Iterator + where + T: IntoIterator, + { + b.into_iter().chain(a).chain(c) } #[inline] diff --git a/yazi-config/src/theme/icons.rs b/yazi-config/src/theme/icons.rs index 34ecfaf6..459da2bb 100644 --- a/yazi-config/src/theme/icons.rs +++ b/yazi-config/src/theme/icons.rs @@ -124,36 +124,26 @@ impl<'de> Deserialize<'de> for Icons { fg_light: Option, } - let mut shadow = Shadow::deserialize(deserializer)?; - Preset::mix(&mut shadow.globs, shadow.prepend_globs, shadow.append_globs); - Preset::mix(&mut shadow.dirs, shadow.prepend_dirs, shadow.append_dirs); - Preset::mix(&mut shadow.files, shadow.prepend_files, shadow.append_files); - Preset::mix(&mut shadow.exts, shadow.prepend_exts, shadow.append_exts); - Preset::mix(&mut shadow.conds, shadow.prepend_conds, shadow.append_conds); + let shadow = Shadow::deserialize(deserializer)?; - let globs = shadow - .globs - .into_iter() + let globs = Preset::mix(shadow.globs, shadow.prepend_globs, shadow.append_globs) .map(|v| { (v.name, Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } }) }) .collect(); - let conds = shadow - .conds - .into_iter() + let conds = Preset::mix(shadow.conds, shadow.prepend_conds, shadow.append_conds) .map(|v| { (v.if_, Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } }) }) .collect(); - fn as_map(v: Vec) -> HashMap { - let mut map = HashMap::with_capacity(v.len()); - for item in v { - map.entry(item.name).or_insert(Icon { - text: item.text, - style: Style { fg: item.fg_dark, ..Default::default() }, - }); + fn as_map(it: impl Iterator) -> HashMap { + let mut map = HashMap::with_capacity(it.size_hint().0); + for v in it { + map + .entry(v.name) + .or_insert(Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } }); } map.shrink_to_fit(); map @@ -161,9 +151,9 @@ impl<'de> Deserialize<'de> for Icons { Ok(Self { globs, - dirs: as_map(shadow.dirs), - files: as_map(shadow.files), - exts: as_map(shadow.exts), + dirs: as_map(Preset::mix(shadow.dirs, shadow.prepend_dirs, shadow.append_dirs)), + files: as_map(Preset::mix(shadow.files, shadow.prepend_files, shadow.append_files)), + exts: as_map(Preset::mix(shadow.exts, shadow.prepend_exts, shadow.append_exts)), conds, }) } diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index 620f9e6d..b722bb41 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -1,7 +1,7 @@ use crossterm::event::KeyCode; use unicode_width::UnicodeWidthStr; use yazi_adapter::Dimension; -use yazi_config::{keymap::{Control, Key}, KEYMAP}; +use yazi_config::{keymap::{Chord, Key}, KEYMAP}; use yazi_shared::{render, render_and, Layer}; use super::HELP_MARGIN; @@ -11,7 +11,7 @@ use crate::input::Input; pub struct Help { pub visible: bool, pub layer: Layer, - pub(super) bindings: Vec<&'static Control>, + pub(super) bindings: Vec<&'static Chord>, // Filter pub(super) keyword: String, @@ -93,7 +93,7 @@ impl Help { // --- Bindings #[inline] - pub fn window(&self) -> &[&Control] { + pub fn window(&self) -> &[&Chord] { let end = (self.offset + Self::limit()).min(self.bindings.len()); &self.bindings[self.offset..end] } diff --git a/yazi-core/src/which/commands/show.rs b/yazi-core/src/which/commands/show.rs index 39f69c47..8fcae121 100644 --- a/yazi-core/src/which/commands/show.rs +++ b/yazi-core/src/which/commands/show.rs @@ -1,12 +1,12 @@ -use std::{collections::HashSet, str::FromStr}; +use std::str::FromStr; -use yazi_config::{keymap::{Control, Key}, KEYMAP}; +use yazi_config::{keymap::{Chord, Key}, KEYMAP}; use yazi_shared::{event::Cmd, render, Layer}; use crate::which::{Which, WhichSorter}; pub struct Opt { - cands: Vec, + cands: Vec, layer: Layer, silent: bool, } @@ -42,16 +42,13 @@ impl Which { render!(); } - pub fn show_with(&mut self, key: &Key, layer: Layer) { - let mut seen = HashSet::new(); - + 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) - .filter(|&c| seen.insert(&c.on)) + .filter(|c| c.on.len() > 1 && c.on[0] == key) .map(|c| c.into()) .collect(); diff --git a/yazi-core/src/which/sorter.rs b/yazi-core/src/which/sorter.rs index d2dc8dfb..5453f325 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::{keymap::ControlCow, which::SortBy, WHICH}; +use yazi_config::{keymap::ChordCow, which::SortBy, WHICH}; use yazi_shared::{natsort, Transliterator}; #[derive(Clone, Copy, PartialEq)] @@ -23,7 +23,7 @@ impl Default for WhichSorter { } impl WhichSorter { - pub(super) fn sort(&self, items: &mut Vec) { + pub(super) fn sort(&self, items: &mut Vec) { if self.by == SortBy::None || items.is_empty() { return; } diff --git a/yazi-core/src/which/which.rs b/yazi-core/src/which/which.rs index 7791263d..83cb2b87 100644 --- a/yazi-core/src/which/which.rs +++ b/yazi-core/src/which/which.rs @@ -1,11 +1,11 @@ -use yazi_config::keymap::{ControlCow, Key}; +use yazi_config::keymap::{ChordCow, Key}; use yazi_shared::{emit, render, render_and, Layer}; #[derive(Default)] pub struct Which { pub(super) layer: Layer, pub times: usize, - pub cands: Vec, + pub cands: Vec, // Visibility pub visible: bool, diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index f6079a69..b9cc69c2 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -1,4 +1,4 @@ -use yazi_config::{keymap::{Control, Key}, KEYMAP}; +use yazi_config::{keymap::{Chord, Key}, KEYMAP}; use yazi_shared::{emit, Layer}; use crate::app::App; @@ -44,13 +44,13 @@ impl<'a> Router<'a> { #[inline] fn matches(&mut self, layer: Layer, key: Key) -> bool { - for ctrl @ Control { on, .. } in KEYMAP.get(layer) { + for ctrl @ Chord { on, .. } in KEYMAP.get(layer) { if on.is_empty() || on[0] != key { continue; } if on.len() > 1 { - self.app.cx.which.show_with(&key, layer); + self.app.cx.which.show_with(key, layer); } else { emit!(Seq(ctrl.to_seq(), layer)); } diff --git a/yazi-fm/src/which/cand.rs b/yazi-fm/src/which/cand.rs index 65fa134b..6d0c9e58 100644 --- a/yazi-fm/src/which/cand.rs +++ b/yazi-fm/src/which/cand.rs @@ -1,13 +1,13 @@ use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Span}, widgets::Widget}; -use yazi_config::{keymap::Control, THEME}; +use yazi_config::{keymap::Chord, THEME}; pub(super) struct Cand<'a> { - cand: &'a Control, + cand: &'a Chord, times: usize, } impl<'a> Cand<'a> { - pub(super) fn new(cand: &'a Control, times: usize) -> Self { Self { times, cand } } + pub(super) fn new(cand: &'a Chord, times: usize) -> Self { Self { times, cand } } fn keys(&self) -> Vec { self.cand.on[self.times..].iter().map(ToString::to_string).collect() diff --git a/yazi-plugin/src/utils/layer.rs b/yazi-plugin/src/utils/layer.rs index 3af6c71d..57a65508 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, IntoLuaMulti, Lua, Table, Value}; use tokio::sync::mpsc; use tokio_stream::wrappers::UnboundedReceiverStream; -use yazi_config::{keymap::{Control, Key}, popup::InputCfg}; +use yazi_config::{keymap::{Chord, Key}, popup::InputCfg}; use yazi_proxy::{AppProxy, InputProxy}; use yazi_shared::{emit, event::Cmd, Debounce, Layer}; @@ -36,7 +36,7 @@ impl Utils { let mut cands = Vec::with_capacity(30); for (i, cand) in t.raw_get::<_, Table>("cands")?.sequence_values::().enumerate() { let cand = cand?; - cands.push(Control { + cands.push(Chord { on: Self::parse_keys(cand.raw_get("on")?)?, run: vec![Cmd::args("callback", &[i]).with_any("tx", tx.clone())], desc: cand.raw_get("desc").ok(),