vim2 (#3594)
- First round of vim tests Add `observe_keystrokes` back to gpui2 Allow multiple actions to match a given key event [[PR Description]] Release Notes: - (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
This commit is contained in:
+22
-4
@@ -18,10 +18,10 @@ use crate::{
|
||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||
DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
|
||||
ForegroundExecutor, KeyBinding, Keymap, LayoutId, Menu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
|
||||
TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
|
||||
WindowHandle, WindowId,
|
||||
ForegroundExecutor, KeyBinding, Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels,
|
||||
Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription,
|
||||
SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window,
|
||||
WindowContext, WindowHandle, WindowId,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, HashSet, VecDeque};
|
||||
@@ -170,6 +170,7 @@ impl App {
|
||||
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
|
||||
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
|
||||
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||
type KeystrokeObserver = Box<dyn FnMut(&KeystrokeEvent, &mut WindowContext) + 'static>;
|
||||
type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
|
||||
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
|
||||
type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
|
||||
@@ -211,6 +212,7 @@ pub struct AppContext {
|
||||
pub(crate) observers: SubscriberSet<EntityId, Handler>,
|
||||
// TypeId is the type of the event that the listener callback expects
|
||||
pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
|
||||
pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
|
||||
pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
|
||||
pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
|
||||
pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
|
||||
@@ -271,6 +273,7 @@ impl AppContext {
|
||||
observers: SubscriberSet::new(),
|
||||
event_listeners: SubscriberSet::new(),
|
||||
release_listeners: SubscriberSet::new(),
|
||||
keystroke_observers: SubscriberSet::new(),
|
||||
global_observers: SubscriberSet::new(),
|
||||
quit_observers: SubscriberSet::new(),
|
||||
layout_id_buffer: Default::default(),
|
||||
@@ -962,6 +965,15 @@ impl AppContext {
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_keystrokes(
|
||||
&mut self,
|
||||
f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
|
||||
) -> Subscription {
|
||||
let (subscription, activate) = self.keystroke_observers.insert((), Box::new(f));
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
|
||||
self.text_style_stack.push(text_style);
|
||||
}
|
||||
@@ -1288,3 +1300,9 @@ pub(crate) struct AnyTooltip {
|
||||
pub view: AnyView,
|
||||
pub cursor_offset: Point<Pixels>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeystrokeEvent {
|
||||
pub keystroke: Keystroke,
|
||||
pub action: Option<Box<dyn Action>>,
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ impl DispatchTree {
|
||||
&mut self,
|
||||
keystroke: &Keystroke,
|
||||
context: &[KeyContext],
|
||||
) -> Option<Box<dyn Action>> {
|
||||
) -> Vec<Box<dyn Action>> {
|
||||
if !self.keystroke_matchers.contains_key(context) {
|
||||
let keystroke_contexts = context.iter().cloned().collect();
|
||||
self.keystroke_matchers.insert(
|
||||
@@ -218,15 +218,15 @@ impl DispatchTree {
|
||||
}
|
||||
|
||||
let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
|
||||
if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(keystroke, context) {
|
||||
if let KeyMatch::Some(actions) = keystroke_matcher.match_keystroke(keystroke, context) {
|
||||
// Clear all pending keystrokes when an action has been found.
|
||||
for keystroke_matcher in self.keystroke_matchers.values_mut() {
|
||||
keystroke_matcher.clear_pending();
|
||||
}
|
||||
|
||||
Some(action)
|
||||
actions
|
||||
} else {
|
||||
None
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ impl KeyBinding {
|
||||
{
|
||||
// If the binding is completed, push it onto the matches list
|
||||
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
|
||||
KeyMatch::Some(self.action.boxed_clone())
|
||||
KeyMatch::Some(vec![self.action.boxed_clone()])
|
||||
} else {
|
||||
KeyMatch::Pending
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@ impl KeystrokeMatcher {
|
||||
}
|
||||
|
||||
let mut pending_key = None;
|
||||
let mut found_actions = Vec::new();
|
||||
|
||||
for binding in keymap.bindings().iter().rev() {
|
||||
for candidate in keystroke.match_candidates() {
|
||||
self.pending_keystrokes.push(candidate.clone());
|
||||
match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
|
||||
KeyMatch::Some(action) => {
|
||||
self.pending_keystrokes.clear();
|
||||
return KeyMatch::Some(action);
|
||||
KeyMatch::Some(mut actions) => {
|
||||
found_actions.append(&mut actions);
|
||||
}
|
||||
KeyMatch::Pending => {
|
||||
pending_key.get_or_insert(candidate);
|
||||
@@ -72,6 +72,11 @@ impl KeystrokeMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
if !found_actions.is_empty() {
|
||||
self.pending_keystrokes.clear();
|
||||
return KeyMatch::Some(found_actions);
|
||||
}
|
||||
|
||||
if let Some(pending_key) = pending_key {
|
||||
self.pending_keystrokes.push(pending_key);
|
||||
}
|
||||
@@ -101,7 +106,7 @@ impl KeystrokeMatcher {
|
||||
pub enum KeyMatch {
|
||||
None,
|
||||
Pending,
|
||||
Some(Box<dyn Action>),
|
||||
Some(Vec<Box<dyn Action>>),
|
||||
}
|
||||
|
||||
impl KeyMatch {
|
||||
|
||||
@@ -130,7 +130,7 @@ impl Platform for TestPlatform {
|
||||
}
|
||||
|
||||
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
|
||||
unimplemented!()
|
||||
self.active_window.lock().clone()
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
|
||||
@@ -3,9 +3,9 @@ use crate::{
|
||||
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
|
||||
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
|
||||
EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
|
||||
ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model,
|
||||
ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId,
|
||||
Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent,
|
||||
Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
|
||||
RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style,
|
||||
SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
|
||||
@@ -495,6 +495,29 @@ impl<'a> WindowContext<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch_keystroke_observers(
|
||||
&mut self,
|
||||
event: &dyn Any,
|
||||
action: Option<Box<dyn Action>>,
|
||||
) {
|
||||
let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.keystroke_observers
|
||||
.clone()
|
||||
.retain(&(), move |callback| {
|
||||
(callback)(
|
||||
&KeystrokeEvent {
|
||||
keystroke: key_down_event.keystroke.clone(),
|
||||
action: action.as_ref().map(|action| action.boxed_clone()),
|
||||
},
|
||||
self,
|
||||
);
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
|
||||
/// that are currently on the stack to be returned to the app.
|
||||
pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
|
||||
@@ -1423,14 +1446,12 @@ impl<'a> WindowContext<'a> {
|
||||
let node = self.window.rendered_frame.dispatch_tree.node(*node_id);
|
||||
if node.context.is_some() {
|
||||
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
if let Some(found) = self
|
||||
let mut new_actions = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_key(&key_down_event.keystroke, &context_stack)
|
||||
{
|
||||
actions.push(found.boxed_clone())
|
||||
}
|
||||
.dispatch_key(&key_down_event.keystroke, &context_stack);
|
||||
actions.append(&mut new_actions);
|
||||
}
|
||||
|
||||
context_stack.pop();
|
||||
@@ -1438,11 +1459,13 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
for action in actions {
|
||||
self.dispatch_action_on_node(node_id, action);
|
||||
self.dispatch_action_on_node(node_id, action.boxed_clone());
|
||||
if !self.propagate_event {
|
||||
self.dispatch_keystroke_observers(event, Some(action));
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.dispatch_keystroke_observers(event, None);
|
||||
}
|
||||
|
||||
fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
|
||||
|
||||
Reference in New Issue
Block a user