feat(input): port keyboard, keybinding and mouse input handling
Ports ZIG/src/input (except macOS keymaps) into ghostty-input: - key, key_mods (incl. RemapSet), the physical keycode table - key_encode: legacy, modifyOtherKeys and kitty keyboard protocol encoders, function key tables - Binding: all keybinding actions with their user-visible docs (for +list-actions), triggers, sets with sequences, chains, leader keys and key tables, and formatting back to config strings - command palette defaults, mouse, paste safety checks, kitty helpers, links, helpgen action docs Deviations (docs/rewrite/notes/ghostty-input.md): Mods.super is named super_; case folding uses char::to_lowercase plus Ghostty's special cases instead of uucode tables; chain parents are trigger paths instead of pointers; a Zig bug where chained leaves inside sequences corrupted the shared prefix when formatting is fixed (covered by a test). Integration left: building encode/paste Options from terminal modes, and implementing the EntryFormatter trait in ghostty-config. Test Plan: - cargo test -p ghostty-input (220 tests; 8 macOS-only skipped) - cargo clippy -p ghostty-input --all-targets -- -D warnings
This commit is contained in:
1 parent
3323c3f525
commit
3afe938b95
21 files changed
+13413
-1
No files matched your search
@@ -0,0 +1,681 @@
|
||||
//! A structure that contains a set of bindings and focuses on fast lookup.
|
||||
//! The use case is that this will be called on EVERY key input to look
|
||||
//! for an associated action so it must be fast. (`Binding.Set`)
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{Action, Elem, Error, Flags, FoldedTrigger, Parser, Trigger, TriggerKey};
|
||||
use crate::key::KeyEvent;
|
||||
|
||||
/// TODO(integration): stand-in for `config/formatter.zig` `EntryFormatter`.
|
||||
/// The config crate implements this to write `keybind = <value>` lines.
|
||||
pub trait EntryFormatter {
|
||||
/// Format a single `key = value` entry where `value` is a string.
|
||||
fn format_entry(&mut self, value: &str);
|
||||
}
|
||||
|
||||
/// Errors from [`Set::append_chain`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum AppendChainError {
|
||||
#[error("no chain parent")]
|
||||
NoChainParent,
|
||||
}
|
||||
|
||||
/// Leaf node of a set is an action to trigger. This is a "leaf" compared
|
||||
/// to the inner nodes which are "leaders" for sequences.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Leaf {
|
||||
pub action: Action,
|
||||
pub flags: Flags,
|
||||
}
|
||||
|
||||
impl Leaf {
|
||||
pub fn hash(&self) -> u64 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = std::collections::hash_map::DefaultHasher::new();
|
||||
self.action.hash_into(&mut h);
|
||||
self.flags.hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
pub fn generic(&self) -> GenericLeaf<'_> {
|
||||
GenericLeaf {
|
||||
flags: self.flags,
|
||||
actions: std::slice::from_ref(&self.action),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Leaf node of a set that triggers multiple actions in sequence.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LeafChained {
|
||||
pub actions: Vec<Action>,
|
||||
pub flags: Flags,
|
||||
}
|
||||
|
||||
impl LeafChained {
|
||||
pub fn generic(&self) -> GenericLeaf<'_> {
|
||||
GenericLeaf {
|
||||
flags: self.flags,
|
||||
actions: &self.actions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic leaf node that can be used to unify the handling of
|
||||
/// leaf and leaf_chained.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GenericLeaf<'a> {
|
||||
pub flags: Flags,
|
||||
pub actions: &'a [Action],
|
||||
}
|
||||
|
||||
impl GenericLeaf<'_> {
|
||||
pub fn actions_slice(&self) -> &[Action] {
|
||||
self.actions
|
||||
}
|
||||
}
|
||||
|
||||
/// The entry type for the forward mapping of trigger to action.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
/// This key is a leader key in a sequence. You must follow the given
|
||||
/// set to find the next key in the sequence.
|
||||
Leader(Box<Set>),
|
||||
|
||||
/// This trigger completes a sequence and the value is the action
|
||||
/// to take along with the flags that may define binding behavior.
|
||||
Leaf(Leaf),
|
||||
|
||||
/// A set of actions to take in response to a trigger.
|
||||
LeafChained(LeafChained),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn as_leaf(&self) -> Option<&Leaf> {
|
||||
match self {
|
||||
Value::Leaf(l) => Some(l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_leader(&self) -> Option<&Set> {
|
||||
match self {
|
||||
Value::Leader(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_leaf_chained(&self) -> Option<&LeafChained> {
|
||||
match self {
|
||||
Value::LeafChained(l) => Some(l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the formatter for the fmt package. This encodes the
|
||||
/// action back into the format used by parse.
|
||||
///
|
||||
/// NOTE: the Zig version has no `leaf_chained` prong (it would not
|
||||
/// compile if used); here only the first action is written.
|
||||
pub fn format(&self) -> String {
|
||||
let mut out = String::new();
|
||||
match self {
|
||||
Value::Leader(set) => {
|
||||
// the leader key was already printed.
|
||||
for (k, v) in &set.bindings {
|
||||
let _ = write!(out, ">{}{}", k.0, v.format());
|
||||
}
|
||||
}
|
||||
Value::Leaf(leaf) => {
|
||||
// action implements the format
|
||||
let _ = write!(out, "={}", leaf.action);
|
||||
}
|
||||
Value::LeafChained(leaf) => {
|
||||
if let Some(a) = leaf.actions.first() {
|
||||
let _ = write!(out, "={a}");
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Writes the configuration entries for the binding
|
||||
/// that this value is part of.
|
||||
///
|
||||
/// The value may be part of multiple configuration entries
|
||||
/// if they're all part of the same prefix sequence (e.g. 'a>b', 'a>c').
|
||||
/// These will result in multiple separate entries in the configuration.
|
||||
///
|
||||
/// `buffer` is temporary storage that is shared between calls to
|
||||
/// nested levels of the set. For example, 'a>b>c=x' and 'a>b>d=y' will
|
||||
/// reuse the 'a>b' written to the buffer before flushing it to the
|
||||
/// formatter with 'c=x' and 'd=y'.
|
||||
pub fn format_entries(&self, buffer: &mut String, formatter: &mut dyn EntryFormatter) {
|
||||
match self {
|
||||
Value::Leader(set) => {
|
||||
// We'll rewind to this position after each sub-entry,
|
||||
// sharing the prefix between siblings.
|
||||
let pos = buffer.len();
|
||||
for (k, v) in &set.bindings {
|
||||
buffer.truncate(pos);
|
||||
let _ = write!(buffer, ">{}", k.0);
|
||||
v.format_entries(buffer, formatter);
|
||||
}
|
||||
buffer.truncate(pos);
|
||||
}
|
||||
|
||||
Value::Leaf(leaf) => {
|
||||
// When we get to the leaf, the buffer contains
|
||||
// the full sequence of keys needed to reach this action.
|
||||
let pos = buffer.len();
|
||||
let _ = write!(buffer, "={}", leaf.action);
|
||||
formatter.format_entry(buffer);
|
||||
buffer.truncate(pos);
|
||||
}
|
||||
|
||||
Value::LeafChained(leaf) => {
|
||||
let pos = buffer.len();
|
||||
for (i, action) in leaf.actions.iter().enumerate() {
|
||||
if i == 0 {
|
||||
let _ = write!(buffer, "={action}");
|
||||
formatter.format_entry(buffer);
|
||||
buffer.truncate(pos);
|
||||
} else {
|
||||
// DEVIATION: Zig rewinds the shared buffer to 0 and
|
||||
// overwrites the sequence prefix; we use a scratch
|
||||
// string so sibling entries keep their prefix.
|
||||
let line = format!("chain={action}");
|
||||
formatter.format_entry(&line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum RecurseError {
|
||||
SequenceUnbind,
|
||||
NoChainParent,
|
||||
UnexpectedEndOfInput,
|
||||
InvalidChainAction,
|
||||
}
|
||||
|
||||
/// A set of bindings with fast lookup.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Set {
|
||||
/// The set of bindings.
|
||||
pub bindings: IndexMap<FoldedTrigger, Value>,
|
||||
|
||||
/// The reverse mapping of action to binding. Note that multiple
|
||||
/// bindings can map to the same action and this map will only have
|
||||
/// the most recently added binding for an action.
|
||||
///
|
||||
/// Sequenced triggers are never present in the reverse map at this time.
|
||||
/// This is a conscious decision since the primary use case of the reverse
|
||||
/// map is to support GUI toolkit keyboard accelerators and no mainstream
|
||||
/// GUI toolkit supports sequences.
|
||||
///
|
||||
/// Performable triggers are also not present in the reverse map. This
|
||||
/// is so that GUI toolkits don't register performable triggers as
|
||||
/// menu shortcuts (the primary use case of the reverse map). GUI toolkits
|
||||
/// such as GTK handle menu shortcuts too early in the event lifecycle
|
||||
/// for performable to work so this is a conscious decision to ease the
|
||||
/// integration with GUI toolkits.
|
||||
reverse: IndexMap<Action, Trigger>,
|
||||
|
||||
/// The chain parent is the information necessary to attach a chained
|
||||
/// action to the proper location in our mapping.
|
||||
///
|
||||
/// DEVIATION: Zig stores raw pointers; here it is the path of triggers
|
||||
/// (through leader sets) from this set to the parent entry. The last
|
||||
/// element is the parent's own trigger.
|
||||
chain_parent: Option<Vec<Trigger>>,
|
||||
}
|
||||
|
||||
impl Clone for Set {
|
||||
/// Deep clone the set. The clone has no chain parent.
|
||||
fn clone(&self) -> Self {
|
||||
Set {
|
||||
bindings: self.bindings.clone(),
|
||||
reverse: self.reverse.clone(),
|
||||
chain_parent: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Set {
|
||||
pub fn new() -> Set {
|
||||
Set::default()
|
||||
}
|
||||
|
||||
/// Deep clone the set (see [`Clone`]).
|
||||
pub fn clone_set(&self) -> Set {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
/// The trigger path of the current chain parent, if any.
|
||||
pub fn chain_parent(&self) -> Option<&[Trigger]> {
|
||||
self.chain_parent.as_deref()
|
||||
}
|
||||
|
||||
/// The (last) trigger of the chain parent, if any.
|
||||
pub fn chain_parent_trigger(&self) -> Option<Trigger> {
|
||||
self.chain_parent.as_ref().and_then(|p| p.last().copied())
|
||||
}
|
||||
|
||||
/// The value of the chain parent, if any.
|
||||
pub fn chain_parent_value(&self) -> Option<&Value> {
|
||||
let path = self.chain_parent.as_ref()?;
|
||||
let (last, prefix) = path.split_last()?;
|
||||
let mut cur = self;
|
||||
for t in prefix {
|
||||
cur = cur.bindings.get(&FoldedTrigger(*t))?.as_leader()?;
|
||||
}
|
||||
cur.bindings.get(&FoldedTrigger(*last))
|
||||
}
|
||||
|
||||
/// Parse a user input binding and add it to the set. This will handle
|
||||
/// the "unbind" case, ensure consumed/unconsumed fields are set correctly,
|
||||
/// handle sequences, etc.
|
||||
///
|
||||
/// Any Error returned is validated before any set modifications are made.
|
||||
pub fn parse_and_put(&mut self, input: &str) -> Result<(), Error> {
|
||||
// To make cleanup easier, we ensure that the full sequence is
|
||||
// valid before making any set modifications.
|
||||
let mut it = Parser::new(input)?;
|
||||
while it.next_elem()?.is_some() {}
|
||||
it.reset();
|
||||
|
||||
match Set::parse_and_put_recurse(self, &mut it, true) {
|
||||
// If we have an updated set (a binding was added) then we store
|
||||
// it for our chain parent.
|
||||
Ok(Some(path)) => self.chain_parent = Some(path),
|
||||
|
||||
// If we didn't update a set then we clear our chain parent since
|
||||
// chaining is no longer valid until a valid binding is saved.
|
||||
// SequenceUnbind reaching the root means we've unbound all the
|
||||
// way up and this put was a success.
|
||||
Ok(None) | Err(RecurseError::SequenceUnbind) => self.chain_parent = None,
|
||||
|
||||
// If our parser input was too short then the format
|
||||
// is invalid because we handle all valid cases. A chain without
|
||||
// a parent or an invalid chain action are format errors too.
|
||||
Err(
|
||||
RecurseError::UnexpectedEndOfInput
|
||||
| RecurseError::NoChainParent
|
||||
| RecurseError::InvalidChainAction,
|
||||
) => return Err(Error::InvalidFormat),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the path (relative to `set`) of the leaf that was
|
||||
/// ultimately updated if a binding was added.
|
||||
fn parse_and_put_recurse(
|
||||
set: &mut Set,
|
||||
it: &mut Parser<'_>,
|
||||
root: bool,
|
||||
) -> Result<Option<Vec<Trigger>>, RecurseError> {
|
||||
let elem = it
|
||||
.next_elem()
|
||||
.ok()
|
||||
.flatten()
|
||||
.ok_or(RecurseError::UnexpectedEndOfInput)?;
|
||||
match elem {
|
||||
Elem::Leader(t) => {
|
||||
// If we have a leader, we need to upsert a set for it.
|
||||
enum Old {
|
||||
None,
|
||||
Leaf(Leaf),
|
||||
Chained(LeafChained),
|
||||
}
|
||||
let key = FoldedTrigger(t);
|
||||
let mut old = Old::None;
|
||||
if let Some(entry) = set.bindings.get_mut(&key) {
|
||||
match entry {
|
||||
// We have an existing leader for this key already
|
||||
// so recurse into this set.
|
||||
Value::Leader(s) => {
|
||||
let r = Set::parse_and_put_recurse(s, it, false);
|
||||
return match r {
|
||||
Ok(path) => Ok(path.map(|p| prepend(t, p))),
|
||||
// Our child put unbound. If our set is empty we
|
||||
// need to dealloc and continue up. If our set is
|
||||
// not empty then we're done.
|
||||
Err(RecurseError::SequenceUnbind) => {
|
||||
if s.bindings.is_empty() {
|
||||
set.remove(t);
|
||||
Err(RecurseError::SequenceUnbind)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
}
|
||||
Value::Leaf(l) => old = Old::Leaf(l.clone()),
|
||||
Value::LeafChained(l) => old = Old::Chained(l.clone()),
|
||||
}
|
||||
// Remove the existing action. Fallthrough as if
|
||||
// we don't have a leader.
|
||||
set.remove(t);
|
||||
}
|
||||
|
||||
// Create our new set for this leader and insert the leader entry
|
||||
set.bindings.insert(key, Value::Leader(Box::default()));
|
||||
|
||||
// Recurse
|
||||
let r = {
|
||||
let Some(Value::Leader(next)) = set.bindings.get_mut(&key) else {
|
||||
unreachable!("leader was just inserted");
|
||||
};
|
||||
Set::parse_and_put_recurse(next, it, false)
|
||||
};
|
||||
match r {
|
||||
Ok(path) => Ok(path.map(|p| prepend(t, p))),
|
||||
// If our action was to unbind, we restore the old
|
||||
// action if we have it.
|
||||
Err(RecurseError::SequenceUnbind) => {
|
||||
set.remove(t);
|
||||
match old {
|
||||
Old::None => {}
|
||||
Old::Leaf(leaf) => set.put_flags(t, leaf.action, leaf.flags),
|
||||
Old::Chained(leaf) => {
|
||||
// Rebuild our chain
|
||||
let mut actions = leaf.actions.into_iter();
|
||||
if let Some(first) = actions.next() {
|
||||
set.put_flags(t, first, leaf.flags);
|
||||
for action in actions {
|
||||
if set.append_chain(action).is_err() {
|
||||
set.remove(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Elem::Binding(b) => match b.action {
|
||||
Action::Unbind => {
|
||||
set.remove(b.trigger);
|
||||
Err(RecurseError::SequenceUnbind)
|
||||
}
|
||||
action => {
|
||||
set.put_flags(b.trigger, action, b.flags);
|
||||
Ok(set.chain_parent.clone())
|
||||
}
|
||||
},
|
||||
|
||||
Elem::Chain(action) => {
|
||||
// Chains can only happen on the root.
|
||||
debug_assert!(root);
|
||||
// Unbind is not valid for chains.
|
||||
if action == Action::Unbind {
|
||||
return Err(RecurseError::InvalidChainAction);
|
||||
}
|
||||
set.append_chain(action)
|
||||
.map_err(|_| RecurseError::NoChainParent)?;
|
||||
Ok(set.chain_parent.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a binding to the set. If the binding already exists then
|
||||
/// this will overwrite it.
|
||||
pub fn put(&mut self, t: Trigger, action: Action) {
|
||||
self.put_flags(t, action, Flags::default());
|
||||
}
|
||||
|
||||
/// Add a binding to the set with explicit flags.
|
||||
pub fn put_flags(&mut self, t: Trigger, action: Action, flags: Flags) {
|
||||
// unbind should never go into the set, it should be handled prior
|
||||
assert_ne!(action, Action::Unbind);
|
||||
|
||||
// This is true if we're going to track this entry as
|
||||
// a reverse mapping. There are certain scenarios we don't.
|
||||
// See the reverse map docs for more information.
|
||||
let track_reverse = !flags.performable;
|
||||
|
||||
self.chain_parent = None;
|
||||
|
||||
let key = FoldedTrigger(t);
|
||||
// If we have an existing binding for this trigger, we have to
|
||||
// update the reverse mapping to remove the old action. A leader or
|
||||
// chained leaf is simply replaced (chained leaves aren't in the
|
||||
// reverse mapping).
|
||||
if track_reverse && matches!(self.bindings.get(&key), Some(Value::Leaf(_))) {
|
||||
let t_hash = t.hash();
|
||||
if let Some(i) = self.reverse.values().position(|v| v.hash() == t_hash) {
|
||||
self.reverse.swap_remove_index(i);
|
||||
}
|
||||
}
|
||||
|
||||
// `insert` keeps the existing key and position (like getOrPut).
|
||||
self.bindings.insert(
|
||||
key,
|
||||
Value::Leaf(Leaf {
|
||||
action: action.clone(),
|
||||
flags,
|
||||
}),
|
||||
);
|
||||
|
||||
if track_reverse {
|
||||
self.reverse.insert(action, t);
|
||||
}
|
||||
|
||||
let stored = self.bindings.get_key_value(&key).map_or(t, |(k, _)| k.0);
|
||||
self.chain_parent = Some(vec![stored]);
|
||||
}
|
||||
|
||||
/// Append a chained action to the prior set action.
|
||||
///
|
||||
/// It is an error if there is no valid prior chain parent.
|
||||
pub fn append_chain(&mut self, action: Action) -> Result<(), AppendChainError> {
|
||||
// Unbind is not a valid chain action; callers must check this.
|
||||
assert_ne!(action, Action::Unbind);
|
||||
|
||||
let path = self
|
||||
.chain_parent
|
||||
.clone()
|
||||
.ok_or(AppendChainError::NoChainParent)?;
|
||||
let (last, prefix) = path.split_last().ok_or(AppendChainError::NoChainParent)?;
|
||||
|
||||
// Navigate to the set that owns the parent entry.
|
||||
let mut parent_set: &mut Set = self;
|
||||
for t in prefix {
|
||||
match parent_set.bindings.get_mut(&FoldedTrigger(*t)) {
|
||||
Some(Value::Leader(s)) => parent_set = s,
|
||||
_ => return Err(AppendChainError::NoChainParent),
|
||||
}
|
||||
}
|
||||
|
||||
let key = FoldedTrigger(*last);
|
||||
let Some((idx, stored_key, value)) = parent_set.bindings.get_full_mut(&key) else {
|
||||
return Err(AppendChainError::NoChainParent);
|
||||
};
|
||||
let stored_key = stored_key.0;
|
||||
let _ = idx;
|
||||
match value {
|
||||
// Leader can never be a chain parent. Verified through various
|
||||
// assertions and unit tests.
|
||||
Value::Leader(_) => unreachable!("leader can never be a chain parent"),
|
||||
|
||||
// If it is already a chained action, we just append the
|
||||
// action. Easy!
|
||||
Value::LeafChained(leaf) => leaf.actions.push(action),
|
||||
|
||||
// If it is a leaf, we need to convert it to a leaf_chained.
|
||||
// We also need to be careful to remove any prior reverse
|
||||
// mappings for this action since chained actions are not
|
||||
// part of the reverse mapping.
|
||||
Value::Leaf(leaf) => {
|
||||
let first = leaf.action.clone();
|
||||
let flags = leaf.flags;
|
||||
|
||||
// Convert to leaf_chained first, before fixing up reverse
|
||||
// mapping, so the fixup doesn't find this (now chained) entry.
|
||||
*value = Value::LeafChained(LeafChained {
|
||||
actions: vec![first.clone(), action],
|
||||
flags,
|
||||
});
|
||||
|
||||
// Clean up our reverse mapping. Chained actions are not
|
||||
// part of the reverse mapping, so we need to fix up the
|
||||
// reverse map (possibly restoring another trigger for the
|
||||
// same action).
|
||||
parent_set.fixup_reverse_for_action(&first, stored_key);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a binding for a given trigger.
|
||||
pub fn get(&self, t: Trigger) -> Option<&Value> {
|
||||
self.bindings.get(&FoldedTrigger(t))
|
||||
}
|
||||
|
||||
/// Get the stored key and value for a given trigger.
|
||||
pub fn get_entry(&self, t: Trigger) -> Option<(&Trigger, &Value)> {
|
||||
self.bindings
|
||||
.get_key_value(&FoldedTrigger(t))
|
||||
.map(|(k, v)| (&k.0, v))
|
||||
}
|
||||
|
||||
/// Get a trigger for the given action. An action can have multiple
|
||||
/// triggers so this will return the first one found.
|
||||
pub fn get_trigger(&self, a: &Action) -> Option<Trigger> {
|
||||
self.reverse.get(a).copied()
|
||||
}
|
||||
|
||||
/// Get an entry for the given key event. This will attempt to find
|
||||
/// a binding using multiple parts of the event in the following order:
|
||||
///
|
||||
/// 1. Translated key (event.key)
|
||||
/// 2. Physical key (event.physical_key)
|
||||
/// 3. Unshifted Unicode codepoint (event.unshifted_codepoint)
|
||||
pub fn get_event(&self, event: &KeyEvent<'_>) -> Option<(&Trigger, &Value)> {
|
||||
let mut trigger = Trigger {
|
||||
mods: event.mods.binding(),
|
||||
key: TriggerKey::Physical(event.key),
|
||||
};
|
||||
if let Some(v) = self.get_entry(trigger) {
|
||||
return Some(v);
|
||||
}
|
||||
|
||||
// If our UTF-8 text is exactly one codepoint, we try to match that.
|
||||
if !event.utf8.is_empty() {
|
||||
let mut it = event.utf8.chars();
|
||||
// No codepoints or multiple codepoints drops to invalid format
|
||||
if let (Some(cp), None) = (it.next(), it.next()) {
|
||||
trigger.key = TriggerKey::Unicode(cp as u32);
|
||||
if let Some(v) = self.get_entry(trigger) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally fallback to the full unshifted codepoint if we have one.
|
||||
// Question: should we be doing this if we have UTF-8 text? I
|
||||
// suspect "no" but we don't currently have any failing scenarios
|
||||
// to verify this.
|
||||
if event.unshifted_codepoint > 0 {
|
||||
trigger.key = TriggerKey::Unicode(event.unshifted_codepoint);
|
||||
if let Some(v) = self.get_entry(trigger) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to catch_all with modifiers first, then without modifiers.
|
||||
trigger.key = TriggerKey::CatchAll;
|
||||
if let Some(v) = self.get_entry(trigger) {
|
||||
return Some(v);
|
||||
}
|
||||
if !trigger.mods.empty() {
|
||||
trigger.mods = crate::key_mods::Mods::default();
|
||||
if let Some(v) = self.get_entry(trigger) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Remove a binding for a given trigger.
|
||||
pub fn remove(&mut self, t: Trigger) {
|
||||
// Removal always resets our chain parent. We could make this
|
||||
// finer grained but the way it is documented is that chaining
|
||||
// must happen directly after sets so this works.
|
||||
self.chain_parent = None;
|
||||
|
||||
let Some(entry) = self.bindings.swap_remove(&FoldedTrigger(t)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match entry {
|
||||
// For a leader removal, the child set is dropped. Leaders are
|
||||
// never part of reverse maps so no other accounting is needed.
|
||||
Value::Leader(_) => {}
|
||||
|
||||
// For an action we need to fix up the reverse mapping.
|
||||
Value::Leaf(leaf) => self.fixup_reverse_for_action(&leaf.action, t),
|
||||
|
||||
// Chained leaves are never in our reverse mapping so no
|
||||
// cleanup is required.
|
||||
Value::LeafChained(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fix up the reverse mapping after removing an action.
|
||||
///
|
||||
/// When an action is removed from a binding (either by removal or by
|
||||
/// converting to a chained action), we need to update the reverse mapping.
|
||||
/// If another binding has the same action, we update the reverse mapping
|
||||
/// to point to that binding. Otherwise, we remove the action from the
|
||||
/// reverse mapping entirely.
|
||||
///
|
||||
/// The `old` parameter is the trigger that was previously bound to this
|
||||
/// action. It is used to check if the reverse mapping still points to
|
||||
/// this trigger; if not, no fixup is needed since the reverse map already
|
||||
/// points to a different trigger for this action.
|
||||
fn fixup_reverse_for_action(&mut self, action: &Action, old: Trigger) {
|
||||
let Some(entry) = self.reverse.get_mut(action) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If our value is not the same as the old trigger, we can
|
||||
// ignore it because our reverse mapping points somewhere else.
|
||||
if !entry.equal(&old) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is the same trigger, so let's now go through our bindings
|
||||
// and try to find another trigger that maps to the same action.
|
||||
let action_hash = action.hash();
|
||||
for (k, v) in &self.bindings {
|
||||
if let Value::Leaf(leaf_search) = v
|
||||
&& leaf_search.action.hash() == action_hash
|
||||
{
|
||||
*entry = k.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No other trigger points to this action so we remove
|
||||
// the reverse mapping completely.
|
||||
self.reverse.swap_remove(action);
|
||||
}
|
||||
}
|
||||
|
||||
fn prepend(t: Trigger, mut p: Vec<Trigger>) -> Vec<Trigger> {
|
||||
p.insert(0, t);
|
||||
p
|
||||
}
|
||||
Reference in new issue
Block a user