fix: filter out the which candidates that overlap with longer key chords (#1562)

This commit is contained in:
三咲雅 · Misaki Masa 2024-08-28 00:08:51 +08:00 committed by GitHub
parent cd7209c040
commit caacd15110
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 120 additions and 114 deletions

5
Cargo.lock generated
View file

@ -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]]

View file

@ -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" ] }

View file

@ -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 }

View file

@ -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 }

View file

@ -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<Regex> = OnceLock::new();
#[derive(Debug, Default, Deserialize)]
pub struct Control {
pub struct Chord {
#[serde(deserialize_with = "super::deserialize_on")]
pub on: Vec<Key>,
#[serde(deserialize_with = "super::deserialize_run")]
@ -17,12 +17,17 @@ pub struct Control {
pub desc: Option<String>,
}
impl Control {
#[inline]
pub fn to_seq(&self) -> VecDeque<Cmd> { 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<H: Hasher>(&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<Cmd> { self.run.iter().map(|c| c.shallow_clone()).collect() }
}

View file

@ -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<Control> for ControlCow {
fn from(c: Control) -> Self { Self::Owned(c) }
impl From<Chord> 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<Cmd> {
match self {
Self::Owned(c) => c.run.into(),

View file

@ -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<Control>,
pub tasks: Vec<Control>,
pub select: Vec<Control>,
pub input: Vec<Control>,
pub confirm: Vec<Control>,
pub help: Vec<Control>,
pub completion: Vec<Control>,
pub manager: Vec<Chord>,
pub tasks: Vec<Chord>,
pub select: Vec<Chord>,
pub input: Vec<Chord>,
pub confirm: Vec<Chord>,
pub help: Vec<Chord>,
pub completion: Vec<Chord>,
}
impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Control> {
pub fn get(&self, layer: Layer) -> &Vec<Chord> {
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<Control>,
keymap: IndexSet<Chord>,
#[serde(default)]
prepend_keymap: Vec<Control>,
prepend_keymap: IndexSet<Chord>,
#[serde(default)]
append_keymap: Vec<Control>,
append_keymap: IndexSet<Chord>,
}
let mut shadow = Shadow::deserialize(deserializer)?;
fn mix(mut a: IndexSet<Chord>, b: IndexSet<Chord>, c: IndexSet<Chord>) -> Vec<Chord> {
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),
})
}
}

View file

@ -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::*;

View file

@ -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::<IndexSet<_>>()))
.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,
})
}
}

View file

@ -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}");

View file

@ -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<T>(a: &mut Vec<T>, b: Vec<T>, c: Vec<T>) {
*a = b.into_iter().chain(mem::take(a)).chain(c).collect();
pub(crate) fn mix<T, E>(a: T, b: T, c: T) -> impl Iterator<Item = E>
where
T: IntoIterator<Item = E>,
{
b.into_iter().chain(a).chain(c)
}
#[inline]

View file

@ -124,36 +124,26 @@ impl<'de> Deserialize<'de> for Icons {
fg_light: Option<Color>,
}
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<ShadowStr>) -> HashMap<String, Icon> {
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<Item = ShadowStr>) -> HashMap<String, Icon> {
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,
})
}

View file

@ -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]
}

View file

@ -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<Control>,
cands: Vec<Chord>,
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();

View file

@ -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<ControlCow>) {
pub(super) fn sort(&self, items: &mut Vec<ChordCow>) {
if self.by == SortBy::None || items.is_empty() {
return;
}

View file

@ -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<ControlCow>,
pub cands: Vec<ChordCow>,
// Visibility
pub visible: bool,

View file

@ -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));
}

View file

@ -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<String> {
self.cand.on[self.times..].iter().map(ToString::to_string).collect()

View file

@ -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::<Table>().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(),