Add KeyContextView (#19872)
Release Notes: - Added `cmd-shift-p debug: Open Key Context View` to help debug custom key bindings https://github.com/user-attachments/assets/de273c97-5b27-45aa-9ff1-f943b0ed7dfe
This commit is contained in:
parent
cf7b0c8971
commit
ce5222f1df
12 changed files with 390 additions and 8 deletions
|
@ -75,6 +75,18 @@ impl Keymap {
|
|||
.filter(move |binding| binding.action().partial_eq(action))
|
||||
}
|
||||
|
||||
/// all bindings for input returns all bindings that might match the input
|
||||
/// (without checking context)
|
||||
pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
|
||||
self.bindings()
|
||||
.rev()
|
||||
.filter_map(|binding| {
|
||||
binding.match_keystrokes(input).filter(|pending| !pending)?;
|
||||
Some(binding.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// bindings_for_input returns a list of bindings that match the given input,
|
||||
/// and a boolean indicating whether or not more bindings might match if
|
||||
/// the input was longer.
|
||||
|
|
|
@ -69,6 +69,11 @@ impl KeyBinding {
|
|||
pub fn action(&self) -> &dyn Action {
|
||||
self.action.as_ref()
|
||||
}
|
||||
|
||||
/// Get the predicate used to match this binding
|
||||
pub fn predicate(&self) -> Option<&KeyBindingContextPredicate> {
|
||||
self.context_predicate.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for KeyBinding {
|
||||
|
|
|
@ -11,9 +11,12 @@ use std::fmt;
|
|||
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct ContextEntry {
|
||||
key: SharedString,
|
||||
value: Option<SharedString>,
|
||||
/// An entry in a KeyContext
|
||||
pub struct ContextEntry {
|
||||
/// The key (or name if no value)
|
||||
pub key: SharedString,
|
||||
/// The value
|
||||
pub value: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for KeyContext {
|
||||
|
@ -39,6 +42,17 @@ impl KeyContext {
|
|||
context
|
||||
}
|
||||
|
||||
/// Returns the primary context entry (usually the name of the component)
|
||||
pub fn primary(&self) -> Option<&ContextEntry> {
|
||||
self.0.iter().find(|p| p.value.is_none())
|
||||
}
|
||||
|
||||
/// Returns everything except the primary context entry.
|
||||
pub fn secondary(&self) -> impl Iterator<Item = &ContextEntry> {
|
||||
let primary = self.primary();
|
||||
self.0.iter().filter(move |&p| Some(p) != primary)
|
||||
}
|
||||
|
||||
/// Parse a key context from a string.
|
||||
/// The key context format is very simple:
|
||||
/// - either a single identifier, such as `StatusBar`
|
||||
|
@ -178,6 +192,20 @@ pub enum KeyBindingContextPredicate {
|
|||
),
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyBindingContextPredicate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Identifier(name) => write!(f, "{}", name),
|
||||
Self::Equal(left, right) => write!(f, "{} == {}", left, right),
|
||||
Self::NotEqual(left, right) => write!(f, "{} != {}", left, right),
|
||||
Self::Not(pred) => write!(f, "!{}", pred),
|
||||
Self::Child(parent, child) => write!(f, "{} > {}", parent, child),
|
||||
Self::And(left, right) => write!(f, "({} && {})", left, right),
|
||||
Self::Or(left, right) => write!(f, "({} || {})", left, right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyBindingContextPredicate {
|
||||
/// Parse a string in the same format as the keymap's context field.
|
||||
///
|
||||
|
|
|
@ -121,6 +121,32 @@ impl Keystroke {
|
|||
})
|
||||
}
|
||||
|
||||
/// Produces a representation of this key that Parse can understand.
|
||||
pub fn unparse(&self) -> String {
|
||||
let mut str = String::new();
|
||||
if self.modifiers.control {
|
||||
str.push_str("ctrl-");
|
||||
}
|
||||
if self.modifiers.alt {
|
||||
str.push_str("alt-");
|
||||
}
|
||||
if self.modifiers.platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
str.push_str("cmd-");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
str.push_str("super-");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
str.push_str("win-");
|
||||
}
|
||||
if self.modifiers.shift {
|
||||
str.push_str("shift-");
|
||||
}
|
||||
str.push_str(&self.key);
|
||||
str
|
||||
}
|
||||
|
||||
/// Returns true if this keystroke left
|
||||
/// the ime system in an incomplete state.
|
||||
pub fn is_ime_in_progress(&self) -> bool {
|
||||
|
|
|
@ -3324,17 +3324,18 @@ impl<'a> WindowContext<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
self.pending_input_changed();
|
||||
self.propagate_event = true;
|
||||
for binding in match_result.bindings {
|
||||
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||
if !self.propagate_event {
|
||||
self.dispatch_keystroke_observers(event, Some(binding.action));
|
||||
self.pending_input_changed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_dispatch_key_event(event, dispatch_path)
|
||||
self.finish_dispatch_key_event(event, dispatch_path);
|
||||
self.pending_input_changed();
|
||||
}
|
||||
|
||||
fn finish_dispatch_key_event(
|
||||
|
@ -3664,6 +3665,22 @@ impl<'a> WindowContext<'a> {
|
|||
receiver
|
||||
}
|
||||
|
||||
/// Returns the current context stack.
|
||||
pub fn context_stack(&self) -> Vec<KeyContext> {
|
||||
let dispatch_tree = &self.window.rendered_frame.dispatch_tree;
|
||||
let node_id = self
|
||||
.window
|
||||
.focus
|
||||
.and_then(|focus_id| dispatch_tree.focusable_node_id(focus_id))
|
||||
.unwrap_or_else(|| dispatch_tree.root_node_id());
|
||||
|
||||
dispatch_tree
|
||||
.dispatch_path(node_id)
|
||||
.iter()
|
||||
.filter_map(move |&node_id| dispatch_tree.node(node_id).context.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all available actions for the focused element.
|
||||
pub fn available_actions(&self) -> Vec<Box<dyn Action>> {
|
||||
let node_id = self
|
||||
|
@ -3704,6 +3721,11 @@ impl<'a> WindowContext<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns key bindings that invoke the given action on the currently focused element.
|
||||
pub fn all_bindings_for_input(&self, input: &[Keystroke]) -> Vec<KeyBinding> {
|
||||
RefCell::borrow(&self.keymap).all_bindings_for_input(input)
|
||||
}
|
||||
|
||||
/// Returns any bindings that would invoke the given action on the given focus handle if it were focused.
|
||||
pub fn bindings_for_action_in(
|
||||
&self,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue