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:
parent
573836a654
commit
13f4a093c8
2 changed files with 127 additions and 13 deletions
|
@ -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.
|
||||||
|
|
|
@ -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,11 +2489,19 @@ 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
|
||||||
{
|
{
|
||||||
self.keystrokes.pop();
|
if self.search {
|
||||||
|
last.key = keystroke.key.clone();
|
||||||
|
self.keystrokes_changed(cx);
|
||||||
|
cx.stop_propagation();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
@ -2511,10 +2544,11 @@ impl KeystrokeInput {
|
||||||
{
|
{
|
||||||
return placeholders;
|
return placeholders;
|
||||||
}
|
}
|
||||||
if self
|
if !self.search
|
||||||
.keystrokes
|
&& self
|
||||||
.last()
|
.keystrokes
|
||||||
.map_or(false, |last| last.key.is_empty())
|
.last()
|
||||||
|
.map_or(false, |last| last.key.is_empty())
|
||||||
{
|
{
|
||||||
return &self.keystrokes[..self.keystrokes.len() - 1];
|
return &self.keystrokes[..self.keystrokes.len() - 1];
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue