- 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:
Conrad Irwin
2023-12-11 15:06:51 -07:00
committed by GitHub
16 changed files with 1430 additions and 1365 deletions
+22 -4
View File
@@ -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>>,
}
+4 -4
View File
@@ -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![]
}
}
+1 -1
View File
@@ -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
}
+9 -4
View File
@@ -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 {
+1 -1
View File
@@ -130,7 +130,7 @@ impl Platform for TestPlatform {
}
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
unimplemented!()
self.active_window.lock().clone()
}
fn open_window(
+32 -9
View File
@@ -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>) {