checkpoint

This commit is contained in:
Junkui Zhang 2025-08-19 21:36:56 +08:00
parent f602c1d69e
commit 8e357210f1
5 changed files with 87 additions and 48 deletions

View file

@ -9171,7 +9171,7 @@ impl Editor {
max_width: Pixels, max_width: Pixels,
cursor_point: Point, cursor_point: Point,
style: &EditorStyle, style: &EditorStyle,
accept_keystroke: Option<&gpui::Keystroke>, accept_keystroke: Option<&gpui::KeybindingKeystroke>,
_window: &Window, _window: &Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) -> Option<AnyElement> { ) -> Option<AnyElement> {

View file

@ -43,10 +43,10 @@ use gpui::{
Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle,
DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId,
GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero,
Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, KeybindingKeystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent,
MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollHandle, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill, Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill,
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background, linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
transparent_black, transparent_black,
}; };
@ -7150,7 +7150,7 @@ fn header_jump_data(
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>); pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
impl AcceptEditPredictionBinding { impl AcceptEditPredictionBinding {
pub fn keystroke(&self) -> Option<&Keystroke> { pub fn keystroke(&self) -> Option<&KeybindingKeystroke> {
if let Some(binding) = self.0.as_ref() { if let Some(binding) = self.0.as_ref() {
match &binding.keystrokes() { match &binding.keystrokes() {
[keystroke, ..] => Some(keystroke), [keystroke, ..] => Some(keystroke),

View file

@ -14,7 +14,7 @@ use gpui::{
Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity, Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity,
EventEmitter, FocusHandle, Focusable, Global, IsZero, EventEmitter, FocusHandle, Focusable, Global, IsZero,
KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or}, KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or},
KeyContext, Keystroke, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, KeyContext, KeybindingKeystroke,Keystroke, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful,
StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, actions, anchored, deferred, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, actions, anchored, deferred,
div, div,
}; };
@ -174,7 +174,7 @@ impl FilterState {
#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)] #[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
struct ActionMapping { struct ActionMapping {
keystrokes: Vec<Keystroke>, keystrokes: Vec<KeybindingKeystroke>,
context: Option<SharedString>, context: Option<SharedString>,
} }
@ -414,12 +414,14 @@ impl Focusable for KeymapEditor {
} }
} }
/// Helper function to check if two keystroke sequences match exactly /// Helper function to check if two keystroke sequences match exactly
fn keystrokes_match_exactly(keystrokes1: &[Keystroke], keystrokes2: &[Keystroke]) -> bool { fn keystrokes_match_exactly(
keystrokes1: &[KeybindingKeystroke],
keystrokes2: &[KeybindingKeystroke],
) -> bool {
keystrokes1.len() == keystrokes2.len() keystrokes1.len() == keystrokes2.len()
&& keystrokes1 && keystrokes1.iter().zip(keystrokes2).all(|(k1, k2)| {
.iter() k1.inner.key == k2.inner.key && k1.inner.modifiers == k2.inner.modifiers
.zip(keystrokes2) })
.all(|(k1, k2)| k1.key == k2.key && k1.modifiers == k2.modifiers)
} }
impl KeymapEditor { impl KeymapEditor {
@ -509,7 +511,7 @@ impl KeymapEditor {
self.filter_editor.read(cx).text(cx) self.filter_editor.read(cx).text(cx)
} }
fn current_keystroke_query(&self, cx: &App) -> Vec<Keystroke> { fn current_keystroke_query(&self, cx: &App) -> Vec<KeybindingKeystroke> {
match self.search_mode { match self.search_mode {
SearchMode::KeyStroke { .. } => self.keystroke_editor.read(cx).keystrokes().to_vec(), SearchMode::KeyStroke { .. } => self.keystroke_editor.read(cx).keystrokes().to_vec(),
SearchMode::Normal => Default::default(), SearchMode::Normal => Default::default(),
@ -530,7 +532,7 @@ impl KeymapEditor {
let keystroke_query = keystroke_query let keystroke_query = keystroke_query
.into_iter() .into_iter()
.map(|keystroke| keystroke.unparse()) .map(|keystroke| keystroke.inner.unparse())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(" "); .join(" ");
@ -554,7 +556,7 @@ impl KeymapEditor {
async fn update_matches( async fn update_matches(
this: WeakEntity<Self>, this: WeakEntity<Self>,
action_query: String, action_query: String,
keystroke_query: Vec<Keystroke>, keystroke_query: Vec<KeybindingKeystroke>,
cx: &mut AsyncApp, cx: &mut AsyncApp,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let action_query = command_palette::normalize_action_query(&action_query); let action_query = command_palette::normalize_action_query(&action_query);
@ -604,12 +606,15 @@ impl KeymapEditor {
let query = &keystroke_query[query_cursor]; let query = &keystroke_query[query_cursor];
let keystroke = &keystrokes[keystroke_cursor]; let keystroke = &keystrokes[keystroke_cursor];
let matches = let matches =
query.modifiers.is_subset_of(&keystroke.modifiers) query.inner.modifiers.is_subset_of(&keystroke.inner.modifiers)
&& ((query.key.is_empty() && ((query.inner.key.is_empty()
|| query.key == keystroke.key) || query.inner.key == keystroke.inner.key)
&& query.key_char.as_ref().is_none_or( && query.inner
|q_kc| q_kc == &keystroke.key, .key_char
)); .as_ref()
.is_none_or(|q_kc| {
q_kc == &keystroke.inner.key
});
if matches { if matches {
found_count += 1; found_count += 1;
query_cursor += 1; query_cursor += 1;
@ -678,7 +683,7 @@ impl KeymapEditor {
.map(KeybindSource::from_meta) .map(KeybindSource::from_meta)
.unwrap_or(KeybindSource::Unknown); .unwrap_or(KeybindSource::Unknown);
let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx); let keystroke_text = ui::text_for_keybinding_keystrokes(key_binding.keystrokes(), cx);
let ui_key_binding = ui::KeyBinding::new_from_gpui(key_binding.clone(), cx) let ui_key_binding = ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
.vim_mode(source == KeybindSource::Vim); .vim_mode(source == KeybindSource::Vim);
@ -1422,7 +1427,7 @@ impl ProcessedBinding {
.map(|keybind| keybind.get_action_mapping()) .map(|keybind| keybind.get_action_mapping())
} }
fn keystrokes(&self) -> Option<&[Keystroke]> { fn keystrokes(&self) -> Option<&[KeybindingKeystroke]> {
self.ui_key_binding() self.ui_key_binding()
.map(|binding| binding.keystrokes.as_slice()) .map(|binding| binding.keystrokes.as_slice())
} }
@ -2220,7 +2225,7 @@ impl KeybindingEditorModal {
Ok(action_arguments) Ok(action_arguments)
} }
fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<Keystroke>> { fn validate_keystrokes(&self, cx: &App) -> anyhow::Result<Vec<KeybindingKeystroke>> {
let new_keystrokes = self let new_keystrokes = self
.keybind_editor .keybind_editor
.read_with(cx, |editor, _| editor.keystrokes().to_vec()); .read_with(cx, |editor, _| editor.keystrokes().to_vec());
@ -2445,11 +2450,21 @@ impl KeybindingEditorModal {
} }
} }
fn remove_key_char(Keystroke { modifiers, key, .. }: Keystroke) -> Keystroke { fn remove_key_char(
Keystroke { KeybindingKeystroke {
inner,
modifiers,
key,
}: KeybindingKeystroke,
) -> KeybindingKeystroke {
KeybindingKeystroke {
inner: Keystroke {
modifiers: inner.modifiers,
key: inner.key,
key_char: None,
},
modifiers, modifiers,
key, key,
..Default::default()
} }
} }

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
Animation, AnimationExt, Context, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, Animation, AnimationExt, Context, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext,
Keystroke, Modifiers, ModifiersChangedEvent, Subscription, Task, actions, KeybindingKeystroke, Keystroke, Modifiers, ModifiersChangedEvent, Subscription, Task, actions,
}; };
use ui::{ use ui::{
ActiveTheme as _, Color, IconButton, IconButtonShape, IconName, IconSize, Label, LabelSize, ActiveTheme as _, Color, IconButton, IconButtonShape, IconName, IconSize, Label, LabelSize,
@ -42,8 +42,8 @@ impl PartialEq for CloseKeystrokeResult {
} }
pub struct KeystrokeInput { pub struct KeystrokeInput {
keystrokes: Vec<Keystroke>, keystrokes: Vec<KeybindingKeystroke>,
placeholder_keystrokes: Option<Vec<Keystroke>>, placeholder_keystrokes: Option<Vec<KeybindingKeystroke>>,
outer_focus_handle: FocusHandle, outer_focus_handle: FocusHandle,
inner_focus_handle: FocusHandle, inner_focus_handle: FocusHandle,
intercept_subscription: Option<Subscription>, intercept_subscription: Option<Subscription>,
@ -70,7 +70,7 @@ impl KeystrokeInput {
const KEYSTROKE_COUNT_MAX: usize = 3; const KEYSTROKE_COUNT_MAX: usize = 3;
pub fn new( pub fn new(
placeholder_keystrokes: Option<Vec<Keystroke>>, placeholder_keystrokes: Option<Vec<KeybindingKeystroke>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@ -97,7 +97,7 @@ impl KeystrokeInput {
} }
} }
pub fn set_keystrokes(&mut self, keystrokes: Vec<Keystroke>, cx: &mut Context<Self>) { pub fn set_keystrokes(&mut self, keystrokes: Vec<KeybindingKeystroke>, cx: &mut Context<Self>) {
self.keystrokes = keystrokes; self.keystrokes = keystrokes;
self.keystrokes_changed(cx); self.keystrokes_changed(cx);
} }
@ -106,7 +106,7 @@ impl KeystrokeInput {
self.search = search; self.search = search;
} }
pub fn keystrokes(&self) -> &[Keystroke] { pub fn keystrokes(&self) -> &[KeybindingKeystroke] {
if let Some(placeholders) = self.placeholder_keystrokes.as_ref() if let Some(placeholders) = self.placeholder_keystrokes.as_ref()
&& self.keystrokes.is_empty() && self.keystrokes.is_empty()
{ {
@ -123,11 +123,15 @@ impl KeystrokeInput {
&self.keystrokes &self.keystrokes
} }
fn dummy(modifiers: Modifiers) -> Keystroke { fn dummy(modifiers: Modifiers) -> KeybindingKeystroke {
Keystroke { KeybindingKeystroke {
inner: Keystroke {
modifiers,
key: "".to_string(),
key_char: None,
},
modifiers, modifiers,
key: "".to_string(), key: "".to_string(),
key_char: None,
} }
} }
@ -297,7 +301,7 @@ impl KeystrokeInput {
return; return;
} }
let mut keystroke = keystroke.clone(); let mut keystroke = KeybindingKeystroke::new(keystroke.clone());
if let Some(last) = self.keystrokes.last() if let Some(last) = self.keystrokes.last()
&& last.key.is_empty() && last.key.is_empty()
&& (!self.search || self.previous_modifiers.modified()) && (!self.search || self.previous_modifiers.modified())
@ -809,9 +813,13 @@ mod tests {
/// Verifies that the keystrokes match the expected strings /// Verifies that the keystrokes match the expected strings
#[track_caller] #[track_caller]
pub fn expect_keystrokes(&mut self, expected: &[&str]) -> &mut Self { pub fn expect_keystrokes(&mut self, expected: &[&str]) -> &mut Self {
let actual = self let actual: Vec<Keystroke> = self.input.read_with(&self.cx, |input, _| {
.input input
.read_with(&self.cx, |input, _| input.keystrokes.clone()); .keystrokes
.iter()
.map(|keystroke| keystroke.inner.clone())
.collect()
});
Self::expect_keystrokes_equal(&actual, expected); Self::expect_keystrokes_equal(&actual, expected);
self self
} }
@ -939,7 +947,7 @@ mod tests {
} }
struct KeystrokeUpdateTracker { struct KeystrokeUpdateTracker {
initial_keystrokes: Vec<Keystroke>, initial_keystrokes: Vec<KeybindingKeystroke>,
_subscription: Subscription, _subscription: Subscription,
input: Entity<KeystrokeInput>, input: Entity<KeystrokeInput>,
received_keystrokes_updated: bool, received_keystrokes_updated: bool,
@ -983,8 +991,8 @@ mod tests {
); );
} }
fn keystrokes_str(ks: &[Keystroke]) -> String { fn keystrokes_str(ks: &[KeybindingKeystroke]) -> String {
ks.iter().map(|ks| ks.unparse()).join(" ") ks.iter().map(|ks| ks.inner.unparse()).join(" ")
} }
} }
} }

View file

@ -1,8 +1,8 @@
use crate::PlatformStyle; use crate::PlatformStyle;
use crate::{Icon, IconName, IconSize, h_flex, prelude::*}; use crate::{Icon, IconName, IconSize, h_flex, prelude::*};
use gpui::{ use gpui::{
Action, AnyElement, App, FocusHandle, Global, IntoElement, KeybindingKeystroke, Modifiers, Action, AnyElement, App, FocusHandle, Global, IntoElement, KeybindingKeystroke, Keystroke,
Window, relative, Modifiers, Window, relative,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -387,10 +387,26 @@ impl KeyIcon {
/// Returns a textual representation of the key binding for the given [`Action`]. /// Returns a textual representation of the key binding for the given [`Action`].
pub fn text_for_action(action: &dyn Action, window: &Window, cx: &App) -> Option<String> { pub fn text_for_action(action: &dyn Action, window: &Window, cx: &App) -> Option<String> {
let key_binding = window.highest_precedence_binding_for_action(action)?; let key_binding = window.highest_precedence_binding_for_action(action)?;
Some(text_for_keystrokes(key_binding.keystrokes(), cx)) Some(text_for_keybinding_keystrokes(key_binding.keystrokes(), cx))
} }
pub fn text_for_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &App) -> String { pub fn text_for_keystrokes(keystrokes: &[Keystroke], cx: &App) -> String {
let platform_style = PlatformStyle::platform();
let vim_enabled = cx.try_global::<VimStyle>().is_some();
keystrokes
.iter()
.map(|keystroke| {
keystroke_text(
&keystroke.modifiers,
&keystroke.key,
platform_style,
vim_enabled,
)
})
.join(" ")
}
pub fn text_for_keybinding_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &App) -> String {
let platform_style = PlatformStyle::platform(); let platform_style = PlatformStyle::platform();
let vim_enabled = cx.try_global::<VimStyle>().is_some(); let vim_enabled = cx.try_global::<VimStyle>().is_some();
keystrokes keystrokes