diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index f3fcd498ccb368a95628fbd60ab6d3bd9b43395b..4df4cc396d22e29321c7526a7193d97bbf2a2dd9 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1477,7 +1477,7 @@ async fn test_host_disconnect( .unwrap() .downcast::() .unwrap(); - assert!(cx_b.read(|cx| editor_b.is_focused(cx))); + assert!(cx_b.read_window(window_id_b, |cx| editor_b.is_focused(cx)).unwrap()); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); assert!(cx_b.is_window_edited(workspace_b.window_id())); diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 48e15ef74d1ff49700b70746835830e27b7a9839..6fc36171326cc24df94cac57ff640f10e1c0fce0 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -43,7 +43,7 @@ impl CommandPalette { pub fn new(focused_view_id: usize, cx: &mut ViewContext) -> Self { let this = cx.weak_handle(); let actions = cx - .available_actions(cx.window_id(), focused_view_id) + .available_actions(focused_view_id) .filter_map(|(name, action, bindings)| { if cx.has_global::() { let filter = cx.global::(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8d491ee7e6230bdea4eb31de5af1f41a6cb8e9ff..eb4fb8f00c0f7eb5fa1fd0e86933c783efc188d2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -42,7 +42,7 @@ use gpui::{ platform::{CursorStyle, MouseButton}, serde_json::{self, json}, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Drawable, Element, Entity, - ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HideHover, HoverState}; @@ -1317,7 +1317,7 @@ impl Editor { self.buffer().read(cx).title(cx) } - pub fn snapshot(&mut self, cx: &mut AppContext) -> EditorSnapshot { + pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot { EditorSnapshot { mode: self.mode, display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index aa4ceb075b970a6234e661a0cac30ef3fb411158..f054552ea37cf0b42a9fee8184452efc09ba7c6b 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -25,7 +25,6 @@ use std::{ use anyhow::{anyhow, Context, Result}; use parking_lot::Mutex; use postage::oneshot; -use smallvec::SmallVec; use smol::prelude::*; use uuid::Uuid; @@ -825,47 +824,6 @@ impl AppContext { .map(|view| view.as_any().type_id()) } - /// Returns an iterator over all of the view ids from the passed view up to the root of the window - /// Includes the passed view itself - fn ancestors(&self, window_id: usize, mut view_id: usize) -> impl Iterator + '_ { - std::iter::once(view_id) - .into_iter() - .chain(std::iter::from_fn(move || { - if let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, view_id)) { - view_id = *parent_id; - Some(view_id) - } else { - None - } - })) - } - - /// Returns the id of the parent of the given view, or none if the given - /// view is the root. - pub fn parent(&self, window_id: usize, view_id: usize) -> Option { - if let Some(ParentId::View(view_id)) = self.parents.get(&(window_id, view_id)) { - Some(*view_id) - } else { - None - } - } - - fn focused_view_id(&self, window_id: usize) -> Option { - self.windows - .get(&window_id) - .and_then(|window| window.focused_view_id) - } - - pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool { - if let Some(focused_view_id) = self.focused_view_id(view.window_id) { - self.ancestors(view.window_id, focused_view_id) - .skip(1) // Skip self id - .any(|parent| parent == view.view_id) - } else { - false - } - } - pub fn active_labeled_tasks<'a>( &'a self, ) -> impl DoubleEndedIterator + 'a { @@ -1231,141 +1189,29 @@ impl AppContext { self.action_deserializers.keys().copied() } - /// Return keystrokes that would dispatch the given action on the given view. - pub(crate) fn keystrokes_for_action( - &mut self, - window_id: usize, - view_id: usize, - action: &dyn Action, - ) -> Option> { - let mut contexts = Vec::new(); - let mut handler_depth = None; - for (i, view_id) in self.ancestors(window_id, view_id).enumerate() { - if let Some(view) = self.views.get(&(window_id, view_id)) { - if let Some(actions) = self.actions.get(&view.as_any().type_id()) { - if actions.contains_key(&action.as_any().type_id()) { - handler_depth = Some(i); - } - } - contexts.push(view.keymap_context(self)); - } - } - - if self.global_actions.contains_key(&action.as_any().type_id()) { - handler_depth = Some(contexts.len()) - } - - self.keystroke_matcher - .bindings_for_action_type(action.as_any().type_id()) - .find_map(|b| { - handler_depth - .map(|highest_handler| { - if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) { - Some(b.keystrokes().into()) - } else { - None - } - }) - .flatten() - }) - } - - pub fn available_actions( - &self, - window_id: usize, - view_id: usize, - ) -> impl Iterator, SmallVec<[&Binding; 1]>)> { - let mut contexts = Vec::new(); - let mut handler_depths_by_action_type = HashMap::::default(); - for (depth, view_id) in self.ancestors(window_id, view_id).enumerate() { - if let Some(view) = self.views.get(&(window_id, view_id)) { - contexts.push(view.keymap_context(self)); - let view_type = view.as_any().type_id(); - if let Some(actions) = self.actions.get(&view_type) { - handler_depths_by_action_type.extend( - actions - .keys() - .copied() - .map(|action_type| (action_type, depth)), - ); - } - } - } - - handler_depths_by_action_type.extend( - self.global_actions - .keys() - .copied() - .map(|action_type| (action_type, contexts.len())), - ); - - self.action_deserializers - .iter() - .filter_map(move |(name, (type_id, deserialize))| { - if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() { - Some(( - *name, - deserialize("{}").ok()?, - self.keystroke_matcher - .bindings_for_action_type(*type_id) - .filter(|b| { - (0..=action_depth).any(|depth| b.match_context(&contexts[depth..])) - }) - .collect(), - )) - } else { - None - } - }) - } - pub fn is_action_available(&self, action: &dyn Action) -> bool { + let mut available_in_window = false; let action_type = action.as_any().type_id(); if let Some(window_id) = self.platform.main_window_id() { - if let Some(focused_view_id) = self.focused_view_id(window_id) { - for view_id in self.ancestors(window_id, focused_view_id) { - if let Some(view) = self.views.get(&(window_id, view_id)) { - let view_type = view.as_any().type_id(); - if let Some(actions) = self.actions.get(&view_type) { - if actions.contains_key(&action_type) { - return true; + available_in_window = self + .read_window(window_id, |cx| { + if let Some(focused_view_id) = cx.focused_view_id() { + for view_id in cx.ancestors(focused_view_id) { + if let Some(view) = cx.views.get(&(window_id, view_id)) { + let view_type = view.as_any().type_id(); + if let Some(actions) = cx.actions.get(&view_type) { + if actions.contains_key(&action_type) { + return true; + } + } } } } - } - } - } - self.global_actions.contains_key(&action_type) - } - - // Traverses the parent tree. Walks down the tree toward the passed - // view calling visit with true. Then walks back up the tree calling visit with false. - // If `visit` returns false this function will immediately return. - // Returns a bool indicating if the traversal was completed early. - fn visit_dispatch_path( - &mut self, - window_id: usize, - view_id: usize, - mut visit: impl FnMut(usize, bool, &mut AppContext) -> bool, - ) -> bool { - // List of view ids from the leaf to the root of the window - let path = self.ancestors(window_id, view_id).collect::>(); - - // Walk down from the root to the leaf calling visit with capture_phase = true - for view_id in path.iter().rev() { - if !visit(*view_id, true, self) { - return false; - } - } - - // Walk up from the leaf to the root calling visit with capture_phase = false - for view_id in path.iter() { - if !visit(*view_id, false, self) { - return false; - } + false + }) + .unwrap_or(false); } - - true + available_in_window || self.global_actions.contains_key(&action_type) } fn actions_mut( @@ -2077,7 +1923,7 @@ impl AppContext { cx.window.is_active = active; if let Some(focused_id) = cx.window.focused_view_id { - for view_id in cx.ancestors(window_id, focused_id).collect::>() { + for view_id in cx.ancestors(focused_id).collect::>() { cx.update_any_view(focused_id, |view, cx| { if active { view.focus_in(focused_id, cx, view_id); @@ -2104,10 +1950,10 @@ impl AppContext { cx.window.focused_view_id = focused_id; let blurred_parents = blurred_id - .map(|blurred_id| cx.ancestors(window_id, blurred_id).collect::>()) + .map(|blurred_id| cx.ancestors(blurred_id).collect::>()) .unwrap_or_default(); let focused_parents = focused_id - .map(|focused_id| cx.ancestors(window_id, focused_id).collect::>()) + .map(|focused_id| cx.ancestors(focused_id).collect::>()) .unwrap_or_default(); if let Some(blurred_id) = blurred_id { @@ -2141,48 +1987,10 @@ impl AppContext { window_id: usize, view_id: Option, action: &dyn Action, - ) -> bool { - self.update(|this| { - if let Some(view_id) = view_id { - this.halt_action_dispatch = false; - this.visit_dispatch_path(window_id, view_id, |view_id, capture_phase, this| { - this.update_window(window_id, |cx| { - cx.update_any_view(view_id, |view, cx| { - let type_id = view.as_any().type_id(); - if let Some((name, mut handlers)) = cx - .actions_mut(capture_phase) - .get_mut(&type_id) - .and_then(|h| h.remove_entry(&action.id())) - { - for handler in handlers.iter_mut().rev() { - cx.halt_action_dispatch = true; - handler(view, action, cx, view_id); - if cx.halt_action_dispatch { - break; - } - } - cx.actions_mut(capture_phase) - .get_mut(&type_id) - .unwrap() - .insert(name, handlers); - } - }) - }); - - !this.halt_action_dispatch - }); - } - - if !this.halt_action_dispatch { - this.halt_action_dispatch = this.dispatch_global_action_any(action); - } - - this.pending_effects - .push_back(Effect::ActionDispatchNotification { - action_id: action.id(), - }); - this.halt_action_dispatch - }) + ) { + self.update_window(window_id, |cx| { + cx.handle_dispatch_action_from_effect(view_id, action) + }); } fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) { @@ -3215,7 +3023,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> { } pub fn parent(&self) -> Option { - self.window_context.parent(self.window_id, self.view_id) + self.window_context.parent(self.view_id) } pub fn window_id(&self) -> usize { @@ -3269,7 +3077,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> { } pub fn is_parent_view_focused(&self) -> bool { - if let Some(parent_view_id) = self.ancestors(self.window_id, self.view_id).next().clone() { + if let Some(parent_view_id) = self.ancestors(self.view_id).next().clone() { self.focused_view_id() == Some(parent_view_id) } else { false @@ -3277,7 +3085,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> { } pub fn focus_parent_view(&mut self) { - let next = self.ancestors(self.window_id, self.view_id).next().clone(); + let next = self.ancestors(self.view_id).next().clone(); if let Some(parent_view_id) = next { let window_id = self.window_id; self.window_context.focus(window_id, Some(parent_view_id)); @@ -3289,7 +3097,7 @@ impl<'a, 'b, 'c, V: View> ViewContext<'a, 'b, 'c, V> { if self.window_id != view.window_id { return false; } - self.ancestors(view.window_id, view.view_id) + self.ancestors(view.view_id) .skip(1) // Skip self id .any(|parent| parent == self.view_id) } @@ -4193,9 +4001,12 @@ impl ViewHandle { }); } + #[cfg(any(test, feature = "test-support"))] pub fn is_focused(&self, cx: &AppContext) -> bool { - cx.focused_view_id(self.window_id) - .map_or(false, |focused_id| focused_id == self.view_id) + cx.read_window(self.window_id, |cx| { + cx.focused_view_id() == Some(self.view_id) + }) + .unwrap_or(false) } } @@ -4312,11 +4123,6 @@ impl AnyViewHandle { TypeId::of::() == self.view_type } - pub fn is_focused(&self, cx: &AppContext) -> bool { - cx.focused_view_id(self.window_id) - .map_or(false, |focused_id| focused_id == self.view_id) - } - pub fn downcast(self) -> Option> { if self.is::() { Some(ViewHandle { @@ -5713,7 +5519,7 @@ mod tests { } let view_events: Arc>> = Default::default(); - let (_, view_1) = cx.add_window(Default::default(), |_| View { + let (window_id, view_1) = cx.add_window(Default::default(), |_| View { events: view_events.clone(), name: "view 1".to_string(), }); @@ -5763,8 +5569,10 @@ mod tests { cx.focus(&view_2); }); - assert!(cx.is_child_focused(&view_1)); - assert!(!cx.is_child_focused(&view_2)); + cx.read_window(window_id, |cx| { + assert!(cx.is_child_focused(&view_1)); + assert!(!cx.is_child_focused(&view_2)); + }); assert_eq!( mem::take(&mut *view_events.lock()), [ @@ -5789,8 +5597,10 @@ mod tests { ); view_1.update(cx, |_, cx| cx.focus(&view_1)); - assert!(!cx.is_child_focused(&view_1)); - assert!(!cx.is_child_focused(&view_2)); + cx.read_window(window_id, |cx| { + assert!(!cx.is_child_focused(&view_1)); + assert!(!cx.is_child_focused(&view_2)); + }); assert_eq!( mem::take(&mut *view_events.lock()), ["view 2 blurred", "view 1 focused"], @@ -5967,11 +5777,9 @@ mod tests { let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 }); let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 }); - cx.handle_dispatch_action_from_effect( - window_id, - Some(view_4.id()), - &Action("bar".to_string()), - ); + cx.update_window(window_id, |cx| { + cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string())) + }); assert_eq!( *actions.borrow(), @@ -5996,11 +5804,9 @@ mod tests { let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 }); actions.borrow_mut().clear(); - cx.handle_dispatch_action_from_effect( - window_id, - Some(view_4.id()), - &Action("bar".to_string()), - ); + cx.update_window(window_id, |cx| { + cx.handle_dispatch_action_from_effect(Some(view_4.id()), &Action("bar".to_string())) + }); assert_eq!( *actions.borrow(), @@ -6197,48 +6003,62 @@ mod tests { Binding::new("c", GlobalAction, Some("View3")), // View 3 does not exist ]); - // Sanity check - assert_eq!( - cx.keystrokes_for_action(window_id, view_1.id(), &Action1) - .unwrap() - .as_slice(), - &[Keystroke::parse("a").unwrap()] - ); - assert_eq!( - cx.keystrokes_for_action(window_id, view_2.id(), &Action2) - .unwrap() - .as_slice(), - &[Keystroke::parse("b").unwrap()] - ); + cx.update_window(window_id, |cx| { + // Sanity check + assert_eq!( + cx.keystrokes_for_action(view_1.id(), &Action1) + .unwrap() + .as_slice(), + &[Keystroke::parse("a").unwrap()] + ); + assert_eq!( + cx.keystrokes_for_action(view_2.id(), &Action2) + .unwrap() + .as_slice(), + &[Keystroke::parse("b").unwrap()] + ); - // The 'a' keystroke propagates up the view tree from view_2 - // to view_1. The action, Action1, is handled by view_1. - assert_eq!( - cx.keystrokes_for_action(window_id, view_2.id(), &Action1) - .unwrap() - .as_slice(), - &[Keystroke::parse("a").unwrap()] - ); + // The 'a' keystroke propagates up the view tree from view_2 + // to view_1. The action, Action1, is handled by view_1. + assert_eq!( + cx.keystrokes_for_action(view_2.id(), &Action1) + .unwrap() + .as_slice(), + &[Keystroke::parse("a").unwrap()] + ); - // Actions that are handled below the current view don't have bindings - assert_eq!( - cx.keystrokes_for_action(window_id, view_1.id(), &Action2), - None - ); + // Actions that are handled below the current view don't have bindings + assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action2), None); - // Actions that are handled in other branches of the tree should not have a binding - assert_eq!( - cx.keystrokes_for_action(window_id, view_2.id(), &GlobalAction), - None - ); + // Actions that are handled in other branches of the tree should not have a binding + assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None); + + // Check that global actions do not have a binding, even if a binding does exist in another view + assert_eq!( + &available_actions(view_1.id(), cx), + &[ + ("test::Action1", vec![Keystroke::parse("a").unwrap()]), + ("test::GlobalAction", vec![]) + ], + ); + + // Check that view 1 actions and bindings are available even when called from view 2 + assert_eq!( + &available_actions(view_2.id(), cx), + &[ + ("test::Action1", vec![Keystroke::parse("a").unwrap()]), + ("test::Action2", vec![Keystroke::parse("b").unwrap()]), + ("test::GlobalAction", vec![]), + ], + ); + }); // Produces a list of actions and key bindings fn available_actions( - window_id: usize, view_id: usize, - cx: &mut AppContext, + cx: &WindowContext, ) -> Vec<(&'static str, Vec)> { - cx.available_actions(window_id, view_id) + cx.available_actions(view_id) .map(|(action_name, _, bindings)| { ( action_name, @@ -6251,25 +6071,6 @@ mod tests { .sorted_by(|(name1, _), (name2, _)| name1.cmp(name2)) .collect() } - - // Check that global actions do not have a binding, even if a binding does exist in another view - assert_eq!( - &available_actions(window_id, view_1.id(), cx), - &[ - ("test::Action1", vec![Keystroke::parse("a").unwrap()]), - ("test::GlobalAction", vec![]) - ], - ); - - // Check that view 1 actions and bindings are available even when called from view 2 - assert_eq!( - &available_actions(window_id, view_2.id(), cx), - &[ - ("test::Action1", vec![Keystroke::parse("a").unwrap()]), - ("test::Action2", vec![Keystroke::parse("b").unwrap()]), - ("test::GlobalAction", vec![]), - ], - ); } #[crate::test(self)] diff --git a/crates/gpui/src/app/menu.rs b/crates/gpui/src/app/menu.rs index c724f487b8de22125cb9b0a137e2760cd139d217..ed23296618428465acdc2e5173e91dcc2a353e24 100644 --- a/crates/gpui/src/app/menu.rs +++ b/crates/gpui/src/app/menu.rs @@ -78,12 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform, move |action| { let mut cx = cx.borrow_mut(); if let Some(main_window_id) = cx.platform.main_window_id() { - if let Some(view_id) = cx - .windows - .get(&main_window_id) - .and_then(|w| w.focused_view_id) - { - cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action); + let dispatched = cx + .update_window(main_window_id, |cx| { + if let Some(view_id) = cx.focused_view_id() { + cx.handle_dispatch_action_from_effect(Some(view_id), action); + true + } else { + false + } + }) + .unwrap_or(false); + + if dispatched { return; } } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 7ca1f32c10c80ae68847ff4bfe2a7f9ffa95a7b2..41358ed37410c867a066a022647c31e5a62ca884 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -2,7 +2,7 @@ use crate::{ elements::AnyRootElement, geometry::rect::RectF, json::{self, ToJson}, - keymap_matcher::{Keystroke, MatchResult}, + keymap_matcher::{Binding, Keystroke, MatchResult}, platform::{ self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseMovedEvent, PromptLevel, WindowBounds, @@ -13,10 +13,10 @@ use crate::{ }, text_layout::TextLayoutCache, util::post_inc, - AnyView, AnyViewHandle, AnyWeakViewHandle, AppContext, Drawable, Entity, ModelContext, - ModelHandle, MouseRegion, MouseRegionId, ParentId, ReadView, SceneBuilder, UpdateModel, - UpdateView, UpgradeViewHandle, View, ViewContext, ViewHandle, WeakViewHandle, - WindowInvalidation, + Action, AnyView, AnyViewHandle, AnyWeakViewHandle, AppContext, Drawable, Effect, Entity, + ModelContext, ModelHandle, MouseRegion, MouseRegionId, ParentId, ReadModel, ReadView, + SceneBuilder, UpdateModel, UpdateView, UpgradeViewHandle, View, ViewContext, ViewHandle, + WeakViewHandle, WindowInvalidation, }; use anyhow::{anyhow, bail, Result}; use collections::{HashMap, HashSet}; @@ -28,7 +28,10 @@ use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use std::ops::{Deref, DerefMut, Range}; +use std::{ + any::TypeId, + ops::{Deref, DerefMut, Range}, +}; use util::ResultExt; use uuid::Uuid; @@ -128,6 +131,12 @@ impl DerefMut for WindowContext<'_, '_> { } } +impl ReadModel for WindowContext<'_, '_> { + fn read_model(&self, handle: &ModelHandle) -> &T { + self.app_context.read_model(handle) + } +} + impl UpdateModel for WindowContext<'_, '_> { fn update_model( &mut self, @@ -230,11 +239,99 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { Some(result) } + /// Return keystrokes that would dispatch the given action on the given view. + pub(crate) fn keystrokes_for_action( + &mut self, + view_id: usize, + action: &dyn Action, + ) -> Option> { + let window_id = self.window_id; + let mut contexts = Vec::new(); + let mut handler_depth = None; + for (i, view_id) in self.ancestors(view_id).enumerate() { + if let Some(view) = self.views.get(&(window_id, view_id)) { + if let Some(actions) = self.actions.get(&view.as_any().type_id()) { + if actions.contains_key(&action.as_any().type_id()) { + handler_depth = Some(i); + } + } + contexts.push(view.keymap_context(self)); + } + } + + if self.global_actions.contains_key(&action.as_any().type_id()) { + handler_depth = Some(contexts.len()) + } + + self.keystroke_matcher + .bindings_for_action_type(action.as_any().type_id()) + .find_map(|b| { + handler_depth + .map(|highest_handler| { + if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) { + Some(b.keystrokes().into()) + } else { + None + } + }) + .flatten() + }) + } + + pub fn available_actions( + &self, + view_id: usize, + ) -> impl Iterator, SmallVec<[&Binding; 1]>)> { + let window_id = self.window_id; + let mut contexts = Vec::new(); + let mut handler_depths_by_action_type = HashMap::::default(); + for (depth, view_id) in self.ancestors(view_id).enumerate() { + if let Some(view) = self.views.get(&(window_id, view_id)) { + contexts.push(view.keymap_context(self)); + let view_type = view.as_any().type_id(); + if let Some(actions) = self.actions.get(&view_type) { + handler_depths_by_action_type.extend( + actions + .keys() + .copied() + .map(|action_type| (action_type, depth)), + ); + } + } + } + + handler_depths_by_action_type.extend( + self.global_actions + .keys() + .copied() + .map(|action_type| (action_type, contexts.len())), + ); + + self.action_deserializers + .iter() + .filter_map(move |(name, (type_id, deserialize))| { + if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() { + Some(( + *name, + deserialize("{}").ok()?, + self.keystroke_matcher + .bindings_for_action_type(*type_id) + .filter(|b| { + (0..=action_depth).any(|depth| b.match_context(&contexts[depth..])) + }) + .collect(), + )) + } else { + None + } + }) + } + pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool { let window_id = self.window_id; if let Some(focused_view_id) = self.focused_view_id() { let dispatch_path = self - .ancestors(window_id, focused_view_id) + .ancestors(focused_view_id) .filter_map(|view_id| { self.views .get(&(window_id, view_id)) @@ -252,11 +349,8 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { MatchResult::Pending => true, MatchResult::Matches(matches) => { for (view_id, action) in matches { - if self.handle_dispatch_action_from_effect( - window_id, - Some(*view_id), - action.as_ref(), - ) { + if self.handle_dispatch_action_from_effect(Some(*view_id), action.as_ref()) + { self.keystroke_matcher.clear_pending(); handled_by = Some(action.boxed_clone()); break; @@ -289,11 +383,11 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] // -> Also updates mouse-related state match &event { - Event::KeyDown(e) => return self.dispatch_key_down(window_id, e), + Event::KeyDown(e) => return self.dispatch_key_down(e), - Event::KeyUp(e) => return self.dispatch_key_up(window_id, e), + Event::KeyUp(e) => return self.dispatch_key_up(e), - Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(window_id, e), + Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(e), Event::MouseDown(e) => { // Click events are weird because they can be fired after a drag event. @@ -615,12 +709,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { any_event_handled } - pub fn dispatch_key_down(&mut self, window_id: usize, event: &KeyDownEvent) -> bool { + pub fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool { + let window_id = self.window_id; if let Some(focused_view_id) = self.window.focused_view_id { - for view_id in self - .ancestors(window_id, focused_view_id) - .collect::>() - { + for view_id in self.ancestors(focused_view_id).collect::>() { if let Some(mut view) = self.views.remove(&(window_id, view_id)) { let handled = view.key_down(event, self, view_id); self.views.insert((window_id, view_id), view); @@ -636,12 +728,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { false } - pub fn dispatch_key_up(&mut self, window_id: usize, event: &KeyUpEvent) -> bool { + pub fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool { + let window_id = self.window_id; if let Some(focused_view_id) = self.window.focused_view_id { - for view_id in self - .ancestors(window_id, focused_view_id) - .collect::>() - { + for view_id in self.ancestors(focused_view_id).collect::>() { if let Some(mut view) = self.views.remove(&(window_id, view_id)) { let handled = view.key_up(event, self, view_id); self.views.insert((window_id, view_id), view); @@ -657,16 +747,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { false } - pub fn dispatch_modifiers_changed( - &mut self, - window_id: usize, - event: &ModifiersChangedEvent, - ) -> bool { + pub fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool { + let window_id = self.window_id; if let Some(focused_view_id) = self.window.focused_view_id { - for view_id in self - .ancestors(window_id, focused_view_id) - .collect::>() - { + for view_id in self.ancestors(focused_view_id).collect::>() { if let Some(mut view) = self.views.remove(&(window_id, view_id)) { let handled = view.modifiers_changed(event, self, view_id); self.views.insert((window_id, view_id), view); @@ -803,10 +887,117 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { self.window.is_fullscreen } + pub(crate) fn handle_dispatch_action_from_effect( + &mut self, + view_id: Option, + action: &dyn Action, + ) -> bool { + if let Some(view_id) = view_id { + self.halt_action_dispatch = false; + self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| { + cx.update_any_view(view_id, |view, cx| { + let type_id = view.as_any().type_id(); + if let Some((name, mut handlers)) = cx + .actions_mut(capture_phase) + .get_mut(&type_id) + .and_then(|h| h.remove_entry(&action.id())) + { + for handler in handlers.iter_mut().rev() { + cx.halt_action_dispatch = true; + handler(view, action, cx, view_id); + if cx.halt_action_dispatch { + break; + } + } + cx.actions_mut(capture_phase) + .get_mut(&type_id) + .unwrap() + .insert(name, handlers); + } + }); + + !cx.halt_action_dispatch + }); + } + + if !self.halt_action_dispatch { + self.halt_action_dispatch = self.dispatch_global_action_any(action); + } + + self.pending_effects + .push_back(Effect::ActionDispatchNotification { + action_id: action.id(), + }); + self.halt_action_dispatch + } + + /// Returns an iterator over all of the view ids from the passed view up to the root of the window + /// Includes the passed view itself + pub(crate) fn ancestors(&self, mut view_id: usize) -> impl Iterator + '_ { + std::iter::once(view_id) + .into_iter() + .chain(std::iter::from_fn(move || { + if let Some(ParentId::View(parent_id)) = + self.parents.get(&(self.window_id, view_id)) + { + view_id = *parent_id; + Some(view_id) + } else { + None + } + })) + } + + /// Returns the id of the parent of the given view, or none if the given + /// view is the root. + pub(crate) fn parent(&self, view_id: usize) -> Option { + if let Some(ParentId::View(view_id)) = self.parents.get(&(self.window_id, view_id)) { + Some(*view_id) + } else { + None + } + } + + // Traverses the parent tree. Walks down the tree toward the passed + // view calling visit with true. Then walks back up the tree calling visit with false. + // If `visit` returns false this function will immediately return. + fn visit_dispatch_path( + &mut self, + view_id: usize, + mut visit: impl FnMut(usize, bool, &mut WindowContext) -> bool, + ) { + // List of view ids from the leaf to the root of the window + let path = self.ancestors(view_id).collect::>(); + + // Walk down from the root to the leaf calling visit with capture_phase = true + for view_id in path.iter().rev() { + if !visit(*view_id, true, self) { + return; + } + } + + // Walk up from the leaf to the root calling visit with capture_phase = false + for view_id in path.iter() { + if !visit(*view_id, false, self) { + return; + } + } + } + pub fn focused_view_id(&self) -> Option { self.window.focused_view_id } + pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool { + if let Some(focused_view_id) = self.focused_view_id() { + self.ancestors(focused_view_id) + .skip(1) // Skip self id + .any(|parent| parent == view.view_id) + } else { + false + } + } + pub fn window_bounds(&self) -> WindowBounds { self.window.platform_window.bounds() } diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index 03c92ad0c19edff0588fdc2d5c32cb6758717b0e..d5fd8c6d56ad1140ffd02e50c2e6574e4372c4a4 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -45,7 +45,7 @@ impl Drawable for KeystrokeLabel { cx: &mut ViewContext, ) -> (Vector2F, Element) { let mut element = if let Some(keystrokes) = - cx.keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref()) + cx.keystrokes_for_action(self.view_id, self.action.as_ref()) { Flex::row() .with_children(keystrokes.iter().map(|keystroke| { diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 99c73ce81aa1e878ed24d7602f38a03989339fa8..aec950c41754af1a244407758c1770f7e88eeece 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -80,9 +80,25 @@ mod tests { watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap, }; use fs::FakeFs; - use gpui::{actions, Action}; + use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext}; use theme::ThemeRegistry; + struct TestView; + + impl Entity for TestView { + type Event = (); + } + + impl View for TestView { + fn ui_name() -> &'static str { + "TestView" + } + + fn render(&mut self, _: &mut ViewContext) -> Element { + Empty::new().boxed() + } + } + #[gpui::test] async fn test_base_keymap(cx: &mut gpui::TestAppContext) { let executor = cx.background(); @@ -148,8 +164,10 @@ mod tests { cx.foreground().run_until_parked(); + let (window_id, _view) = cx.add_window(|_| TestView); + // Test loading the keymap base at all - cx.update(|cx| { + cx.read_window(window_id, |cx| { assert_key_bindings_for( cx, vec![("backspace", &A), ("k", &ActivatePreviousPane)], @@ -177,7 +195,7 @@ mod tests { cx.foreground().run_until_parked(); - cx.update(|cx| { + cx.read_window(window_id, |cx| { assert_key_bindings_for( cx, vec![("backspace", &B), ("k", &ActivatePreviousPane)], @@ -201,7 +219,7 @@ mod tests { cx.foreground().run_until_parked(); - cx.update(|cx| { + cx.read_window(window_id, |cx| { assert_key_bindings_for( cx, vec![("backspace", &B), ("[", &ActivatePrevItem)], @@ -211,14 +229,14 @@ mod tests { } fn assert_key_bindings_for<'a>( - cx: &mut AppContext, + cx: &WindowContext, actions: Vec<(&'static str, &'a dyn Action)>, line: u32, ) { for (key, action) in actions { // assert that... assert!( - cx.available_actions(0, 0).any(|(_, bound_action, b)| { + cx.available_actions(0).any(|(_, bound_action, b)| { // action names match... bound_action.name() == action.name() && bound_action.namespace() == action.namespace() diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 6c06e9f0cb10489d199d5038987d90a33f51467e..8c170a9ee39f7fb9138a219cfe90b813e0b1d7cd 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -1,7 +1,7 @@ use crate::StatusItemView; use gpui::{ elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Entity, Subscription, View, ViewContext, ViewHandle, + AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -21,8 +21,8 @@ pub trait SidebarItem: View { pub trait SidebarItemHandle { fn id(&self) -> usize; - fn should_show_badge(&self, cx: &AppContext) -> bool; - fn is_focused(&self, cx: &AppContext) -> bool; + fn should_show_badge(&self, cx: &WindowContext) -> bool; + fn is_focused(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -34,11 +34,11 @@ where self.id() } - fn should_show_badge(&self, cx: &AppContext) -> bool { + fn should_show_badge(&self, cx: &WindowContext) -> bool { self.read(cx).should_show_badge(cx) } - fn is_focused(&self, cx: &AppContext) -> bool { + fn is_focused(&self, cx: &WindowContext) -> bool { ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx) } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e1f14d434a834fd16b8184a364943d9ac8cfd9aa..49404d6ebcd165657b36c75e8a813055b301d86d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -747,12 +747,9 @@ mod tests { }) .await; assert_eq!(cx.window_ids().len(), 2); - let workspace_1 = cx - .read_window(window_id, |cx| cx.root_view().clone()) - .unwrap() - .downcast::() - .unwrap(); - workspace_1.read_with(cx, |workspace, cx| { + cx.read_window(window_id, |cx| { + let workspace = cx.root_view().clone().downcast::().unwrap(); + let workspace = workspace.read(cx); assert_eq!( workspace .worktrees(cx)