Improve keystroke search in keymap editor (#34567)

This PR improves Keystroke search by:

1.  Allow searching by modifiers without additional keys.
2. Take match count into consideration when deciding if we should show
an action as a search match.
3. Take order into consideration as well.

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
Anthony Eid 2025-07-16 14:19:41 -04:00 committed by GitHub
parent 573836a654
commit 13f4a093c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 127 additions and 13 deletions

View file

@ -417,6 +417,17 @@ impl Modifiers {
self.control || self.alt || self.shift || self.platform || self.function self.control || self.alt || self.shift || self.platform || self.function
} }
/// Returns the XOR of two modifier sets
pub fn xor(&self, other: &Modifiers) -> Modifiers {
Modifiers {
control: self.control ^ other.control,
alt: self.alt ^ other.alt,
shift: self.shift ^ other.shift,
platform: self.platform ^ other.platform,
function: self.function ^ other.function,
}
}
/// Whether the semantically 'secondary' modifier key is pressed. /// Whether the semantically 'secondary' modifier key is pressed.
/// ///
/// On macOS, this is the command key. /// On macOS, this is the command key.

View file

@ -470,11 +470,22 @@ impl KeymapEditor {
}, },
) )
} else { } else {
keystroke_query.iter().all(|key| { let key_press_query =
keystrokes.iter().any(|keystroke| { KeyPressIterator::new(keystroke_query.as_slice());
keystroke.key == key.key let mut last_match_idx = 0;
&& keystroke.modifiers == key.modifiers
}) key_press_query.into_iter().all(|key| {
let key_presses = KeyPressIterator::new(keystrokes);
key_presses.into_iter().enumerate().any(
|(index, keystroke)| {
if last_match_idx > index || keystroke != key {
return false;
}
last_match_idx = index;
true
},
)
}) })
} }
}) })
@ -2313,6 +2324,16 @@ enum CloseKeystrokeResult {
None, None,
} }
#[derive(PartialEq, Eq, Debug, Clone)]
enum KeyPress<'a> {
Alt,
Control,
Function,
Shift,
Platform,
Key(&'a String),
}
struct KeystrokeInput { struct KeystrokeInput {
keystrokes: Vec<Keystroke>, keystrokes: Vec<Keystroke>,
placeholder_keystrokes: Option<Vec<Keystroke>>, placeholder_keystrokes: Option<Vec<Keystroke>>,
@ -2322,6 +2343,7 @@ struct KeystrokeInput {
intercept_subscription: Option<Subscription>, intercept_subscription: Option<Subscription>,
_focus_subscriptions: [Subscription; 2], _focus_subscriptions: [Subscription; 2],
search: bool, search: bool,
/// Handles tripe escape to stop recording
close_keystrokes: Option<Vec<Keystroke>>, close_keystrokes: Option<Vec<Keystroke>>,
close_keystrokes_start: Option<usize>, close_keystrokes_start: Option<usize>,
} }
@ -2443,11 +2465,14 @@ impl KeystrokeInput {
&& last.key.is_empty() && last.key.is_empty()
&& keystrokes_len <= Self::KEYSTROKE_COUNT_MAX && keystrokes_len <= Self::KEYSTROKE_COUNT_MAX
{ {
if !event.modifiers.modified() { if self.search {
last.modifiers = last.modifiers.xor(&event.modifiers);
} else if !event.modifiers.modified() {
self.keystrokes.pop(); self.keystrokes.pop();
} else { } else {
last.modifiers = event.modifiers; last.modifiers = event.modifiers;
} }
self.keystrokes_changed(cx); self.keystrokes_changed(cx);
} else if keystrokes_len < Self::KEYSTROKE_COUNT_MAX { } else if keystrokes_len < Self::KEYSTROKE_COUNT_MAX {
self.keystrokes.push(Self::dummy(event.modifiers)); self.keystrokes.push(Self::dummy(event.modifiers));
@ -2464,12 +2489,20 @@ impl KeystrokeInput {
) { ) {
let close_keystroke_result = self.handle_possible_close_keystroke(keystroke, window, cx); let close_keystroke_result = self.handle_possible_close_keystroke(keystroke, window, cx);
if close_keystroke_result != CloseKeystrokeResult::Close { if close_keystroke_result != CloseKeystrokeResult::Close {
if let Some(last) = self.keystrokes.last() let key_len = self.keystrokes.len();
if let Some(last) = self.keystrokes.last_mut()
&& last.key.is_empty() && last.key.is_empty()
&& self.keystrokes.len() <= Self::KEYSTROKE_COUNT_MAX && key_len <= Self::KEYSTROKE_COUNT_MAX
{ {
if self.search {
last.key = keystroke.key.clone();
self.keystrokes_changed(cx);
cx.stop_propagation();
return;
} else {
self.keystrokes.pop(); self.keystrokes.pop();
} }
}
if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX { if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX {
if close_keystroke_result == CloseKeystrokeResult::Partial if close_keystroke_result == CloseKeystrokeResult::Partial
&& self.close_keystrokes_start.is_none() && self.close_keystrokes_start.is_none()
@ -2511,7 +2544,8 @@ impl KeystrokeInput {
{ {
return placeholders; return placeholders;
} }
if self if !self.search
&& self
.keystrokes .keystrokes
.last() .last()
.map_or(false, |last| last.key.is_empty()) .map_or(false, |last| last.key.is_empty())
@ -2957,3 +2991,72 @@ mod persistence {
} }
} }
} }
/// Iterator that yields KeyPress values from a slice of Keystrokes
struct KeyPressIterator<'a> {
keystrokes: &'a [Keystroke],
current_keystroke_index: usize,
current_key_press_index: usize,
}
impl<'a> KeyPressIterator<'a> {
fn new(keystrokes: &'a [Keystroke]) -> Self {
Self {
keystrokes,
current_keystroke_index: 0,
current_key_press_index: 0,
}
}
}
impl<'a> Iterator for KeyPressIterator<'a> {
type Item = KeyPress<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let keystroke = self.keystrokes.get(self.current_keystroke_index)?;
match self.current_key_press_index {
0 => {
self.current_key_press_index = 1;
if keystroke.modifiers.platform {
return Some(KeyPress::Platform);
}
}
1 => {
self.current_key_press_index = 2;
if keystroke.modifiers.alt {
return Some(KeyPress::Alt);
}
}
2 => {
self.current_key_press_index = 3;
if keystroke.modifiers.control {
return Some(KeyPress::Control);
}
}
3 => {
self.current_key_press_index = 4;
if keystroke.modifiers.shift {
return Some(KeyPress::Shift);
}
}
4 => {
self.current_key_press_index = 5;
if keystroke.modifiers.function {
return Some(KeyPress::Function);
}
}
_ => {
self.current_keystroke_index += 1;
self.current_key_press_index = 0;
if keystroke.key.is_empty() {
continue;
}
return Some(KeyPress::Key(&keystroke.key));
}
}
}
}
}