micro optimization fixes
This commit is contained in:
1 parent
0d7203f84b
commit
da60ecb719
36 files changed
+1781
-521
No files matched your search
@@ -1,5 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
|
||||
mod index;
|
||||
pub(crate) use index::BindingSet;
|
||||
|
||||
impl Config {
|
||||
pub fn bindings(&self, input: &InputSettings) -> Result<Vec<Binding>, ConfigError> {
|
||||
let mut commands = HashMap::from([
|
||||
@@ -280,7 +285,7 @@ pub(super) fn parse_action(
|
||||
let argv = commands
|
||||
.get(command)
|
||||
.ok_or_else(|| ConfigError::Binding(format!("unknown command {command:?}")))?;
|
||||
Ok(BindingAction::Spawn(argv.clone()))
|
||||
Ok(BindingAction::Spawn(argv.clone().into()))
|
||||
}
|
||||
"close" => {
|
||||
no_argument()?;
|
||||
@@ -440,7 +445,7 @@ pub(super) struct BindingModifiers {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BindingAction {
|
||||
None,
|
||||
Spawn(Vec<String>),
|
||||
Spawn(Arc<[String]>),
|
||||
Close,
|
||||
Exit,
|
||||
Focus(ferese_layout::Direction),
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
use super::*;
|
||||
|
||||
/// Immutable bindings and their keyboard index are replaced together on reload.
|
||||
/// The ordered slice remains available for gestures and the shortcut guide.
|
||||
pub(crate) struct BindingSet {
|
||||
ordered: Vec<Binding>,
|
||||
keyboard: HashMap<BindingIdentity, usize>,
|
||||
}
|
||||
|
||||
impl From<Vec<Binding>> for BindingSet {
|
||||
fn from(ordered: Vec<Binding>) -> Self {
|
||||
let mut keyboard = HashMap::with_capacity(ordered.len());
|
||||
|
||||
for (index, binding) in ordered.iter().enumerate() {
|
||||
let identity = binding_identity_for_runtime(binding);
|
||||
|
||||
if identity.match_mode != BindingMatch::Swipe {
|
||||
keyboard.entry(identity).or_insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
Self { ordered, keyboard }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for BindingSet {
|
||||
type Target = [Binding];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ordered
|
||||
}
|
||||
}
|
||||
|
||||
impl BindingSet {
|
||||
pub(crate) fn action_for_key(
|
||||
&self,
|
||||
keycode: Keycode,
|
||||
symbols: &[u32],
|
||||
logo: bool,
|
||||
ctrl: bool,
|
||||
alt: bool,
|
||||
shift: bool,
|
||||
) -> Option<&BindingAction> {
|
||||
let modifiers = BindingModifiers { logo, ctrl, alt, shift };
|
||||
let physical = self.keyboard.get(&BindingIdentity {
|
||||
modifiers,
|
||||
match_mode: BindingMatch::Physical,
|
||||
key: keycode.raw(),
|
||||
});
|
||||
let symbolic = symbols.iter().filter_map(|&key| {
|
||||
self.keyboard.get(&BindingIdentity {
|
||||
modifiers,
|
||||
match_mode: BindingMatch::Keysym,
|
||||
key,
|
||||
})
|
||||
});
|
||||
// Preserve list precedence across physical keys and all raw symbols;
|
||||
// neither hash iteration order nor symbol order determines the winner.
|
||||
physical
|
||||
.into_iter()
|
||||
.chain(symbolic)
|
||||
.min()
|
||||
.map(|&index| &self.ordered[index].action)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn binding_for(trigger: BindingTrigger, action: BindingAction) -> Binding {
|
||||
Binding {
|
||||
modifiers: BindingModifiers::default(),
|
||||
trigger,
|
||||
action,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyboard_index_preserves_order_across_symbols_and_physical_keys() {
|
||||
let code = Keycode::new(38);
|
||||
let bindings = vec![
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_b), BindingAction::Close),
|
||||
binding_for(BindingTrigger::Physical(code), BindingAction::ToggleFloating),
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_a), BindingAction::Exit),
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_b), BindingAction::None),
|
||||
];
|
||||
|
||||
for ordered in [bindings.clone(), bindings.into_iter().rev().collect()] {
|
||||
let index = BindingSet::from(ordered);
|
||||
|
||||
for symbols in [
|
||||
vec![keysyms::KEY_a, keysyms::KEY_b],
|
||||
vec![keysyms::KEY_b, keysyms::KEY_a],
|
||||
vec![],
|
||||
] {
|
||||
let expected = index
|
||||
.iter()
|
||||
.find(|binding| binding.matches(code, &symbols, false, false, false, false));
|
||||
assert_eq!(
|
||||
index.action_for_key(code, &symbols, false, false, false, false),
|
||||
expected.map(|binding| &binding.action)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexed_lookup_matches_linear_lookup_for_all_modifier_combinations() {
|
||||
let config = Config::default();
|
||||
let mut bindings = config.bindings(&config.input_settings().unwrap()).unwrap();
|
||||
bindings.extend([
|
||||
binding_for(BindingTrigger::Physical(Keycode::new(38)), BindingAction::None),
|
||||
binding_for(BindingTrigger::Keysym(keysyms::KEY_a), BindingAction::Close),
|
||||
binding_for(
|
||||
BindingTrigger::Swipe {
|
||||
fingers: 3,
|
||||
direction: crate::gestures::SwipeDirection::Left,
|
||||
},
|
||||
BindingAction::Focus(Direction::Left),
|
||||
),
|
||||
]);
|
||||
let index = BindingSet::from(bindings);
|
||||
|
||||
for bits in 0..16 {
|
||||
let (logo, ctrl, alt, shift) = (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0, bits & 8 != 0);
|
||||
|
||||
for code in 8..100 {
|
||||
for symbols in [
|
||||
vec![],
|
||||
vec![keysyms::KEY_Return],
|
||||
vec![keysyms::KEY_b, keysyms::KEY_a],
|
||||
vec![keysyms::KEY_a, keysyms::KEY_Tab],
|
||||
vec![keysyms::KEY_NoSymbol],
|
||||
] {
|
||||
let code = Keycode::new(code);
|
||||
let expected = index
|
||||
.iter()
|
||||
.find(|binding| binding.matches(code, &symbols, logo, ctrl, alt, shift));
|
||||
assert_eq!(
|
||||
index.action_for_key(code, &symbols, logo, ctrl, alt, shift),
|
||||
expected.map(|binding| &binding.action)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replacing_bindings_discards_old_index_entries() {
|
||||
let code = Keycode::new(38);
|
||||
let mut bindings = BindingSet::from(vec![binding_for(BindingTrigger::Physical(code), BindingAction::Close)]);
|
||||
assert_eq!(
|
||||
bindings.action_for_key(code, &[], false, false, false, false),
|
||||
Some(&BindingAction::Close)
|
||||
);
|
||||
bindings = BindingSet::from(vec![binding_for(
|
||||
BindingTrigger::Keysym(keysyms::KEY_b),
|
||||
BindingAction::None,
|
||||
)]);
|
||||
assert_eq!(bindings.action_for_key(code, &[], false, false, false, false), None);
|
||||
assert_eq!(
|
||||
bindings.action_for_key(code, &[keysyms::KEY_b], false, false, false, false),
|
||||
Some(&BindingAction::None)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cloning_spawn_actions_shares_the_command_storage() {
|
||||
let action = BindingAction::Spawn(vec!["foot".into(), "--app-id".into(), "work".into()].into());
|
||||
let cloned = action.clone();
|
||||
let (BindingAction::Spawn(original), BindingAction::Spawn(cloned)) = (&action, cloned) else {
|
||||
unreachable!();
|
||||
};
|
||||
assert!(Arc::ptr_eq(original, &cloned));
|
||||
assert_eq!(original[0], "foot");
|
||||
assert_eq!(original.len(), 3);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user