diff --git a/Cargo.lock b/Cargo.lock index a03291f253..f94e865ffe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9455,7 +9455,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.95.0" +version = "0.95.3" dependencies = [ "activity_indicator", "ai", diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index ed5e560218..cf6dd1799c 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -263,7 +263,7 @@ impl ActiveCall { .borrow_mut() .take() .ok_or_else(|| anyhow!("no incoming call"))?; - self.report_call_event_for_room("decline incoming", call.room_id, cx); + Self::report_call_event_for_room("decline incoming", call.room_id, &self.client, cx); self.client.send(proto::DeclineCall { room_id: call.room_id, })?; @@ -373,22 +373,29 @@ impl ActiveCall { self.room.as_ref().map(|(room, _)| room) } + pub fn client(&self) -> Arc { + self.client.clone() + } + pub fn pending_invites(&self) -> &HashSet { &self.pending_invites } fn report_call_event(&self, operation: &'static str, cx: &AppContext) { if let Some(room) = self.room() { - self.report_call_event_for_room(operation, room.read(cx).id(), cx) + Self::report_call_event_for_room(operation, room.read(cx).id(), &self.client, cx) } } - fn report_call_event_for_room(&self, operation: &'static str, room_id: u64, cx: &AppContext) { - let telemetry = self.client.telemetry(); + pub fn report_call_event_for_room( + operation: &'static str, + room_id: u64, + client: &Arc, + cx: &AppContext, + ) { + let telemetry = client.telemetry(); let telemetry_settings = *settings::get::(cx); - let event = ClickhouseEvent::Call { operation, room_id }; - telemetry.report_clickhouse_event(event, telemetry_settings); } } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 959f4cc783..dc5154d96f 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -40,6 +40,7 @@ lazy_static! { struct ClickhouseEventRequestBody { token: &'static str, installation_id: Option>, + is_staff: Option, app_version: Option>, os_name: &'static str, os_version: Option>, @@ -224,6 +225,7 @@ impl Telemetry { &ClickhouseEventRequestBody { token: ZED_SECRET_CLIENT_TOKEN, installation_id: state.installation_id.clone(), + is_staff: state.is_staff.clone(), app_version: state.app_version.clone(), os_name: state.os_name, os_version: state.os_version.clone(), diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3f5ca17a20..7608fdbfee 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -11,7 +11,7 @@ mod sharing_status_indicator; use call::{ActiveCall, Room}; pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu}; -use gpui::{actions, AppContext}; +use gpui::{actions, AppContext, Task}; use std::sync::Arc; use util::ResultExt; use workspace::AppState; @@ -44,9 +44,30 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { - ActiveCall::global(cx).update(cx, |call, cx| { - call.toggle_screen_sharing(cx); - }); + let call = ActiveCall::global(cx).read(cx); + if let Some(room) = call.room().cloned() { + let client = call.client(); + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + ActiveCall::report_call_event_for_room( + "disable screen share", + room.id(), + &client, + cx, + ); + Task::ready(room.unshare_screen(cx)) + } else { + ActiveCall::report_call_event_for_room( + "enable screen share", + room.id(), + &client, + cx, + ); + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } } pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index ed9aa85a89..3826dae2aa 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -33,6 +33,7 @@ pub trait GitRepository: Send { fn statuses(&self) -> Option>; fn status(&self, path: &RepoPath) -> Result>; + fn branches(&self) -> Result> { Ok(vec![]) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 640614324f..b40a67db61 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1073,7 +1073,7 @@ impl AppContext { pub fn is_action_available(&self, action: &dyn Action) -> bool { let mut available_in_window = false; - let action_type = action.as_any().type_id(); + let action_id = action.id(); if let Some(window_id) = self.platform.main_window_id() { available_in_window = self .read_window(window_id, |cx| { @@ -1083,7 +1083,7 @@ impl AppContext { cx.views_metadata.get(&(window_id, view_id)) { if let Some(actions) = cx.actions.get(&view_metadata.type_id) { - if actions.contains_key(&action_type) { + if actions.contains_key(&action_id) { return true; } } @@ -1094,7 +1094,7 @@ impl AppContext { }) .unwrap_or(false); } - available_in_window || self.global_actions.contains_key(&action_type) + available_in_window || self.global_actions.contains_key(&action_id) } fn actions_mut( @@ -3399,7 +3399,7 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> { for (i, view_id) in self.ancestors(view_id).enumerate() { if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) { if let Some(actions) = self.actions.get(&view_metadata.type_id) { - if actions.contains_key(&action.as_any().type_id()) { + if actions.contains_key(&action.id()) { handler_depth = Some(i); } } @@ -3407,12 +3407,12 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> { } } - if self.global_actions.contains_key(&action.as_any().type_id()) { + if self.global_actions.contains_key(&action.id()) { handler_depth = Some(contexts.len()) } self.keystroke_matcher - .bindings_for_action_type(action.as_any().type_id()) + .bindings_for_action(action.id()) .find_map(|b| { let highest_handler = handler_depth?; if action.eq(b.action()) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 58d7bb4c40..5cb02d5acd 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -14,8 +14,8 @@ use crate::{ text_layout::TextLayoutCache, util::post_inc, Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect, - Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, NoAction, SceneBuilder, - Subscription, View, ViewContext, ViewHandle, WindowInvalidation, + Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription, + View, ViewContext, ViewHandle, WindowInvalidation, }; use anyhow::{anyhow, bail, Result}; use collections::{HashMap, HashSet}; @@ -363,17 +363,13 @@ impl<'a> WindowContext<'a> { ) -> Vec<(&'static str, Box, SmallVec<[Binding; 1]>)> { let window_id = self.window_id; let mut contexts = Vec::new(); - let mut handler_depths_by_action_type = HashMap::::default(); + let mut handler_depths_by_action_id = HashMap::::default(); for (depth, view_id) in self.ancestors(view_id).enumerate() { if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) { contexts.push(view_metadata.keymap_context.clone()); if let Some(actions) = self.actions.get(&view_metadata.type_id) { - handler_depths_by_action_type.extend( - actions - .keys() - .copied() - .map(|action_type| (action_type, depth)), - ); + handler_depths_by_action_id + .extend(actions.keys().copied().map(|action_id| (action_id, depth))); } } else { log::error!( @@ -383,21 +379,21 @@ impl<'a> WindowContext<'a> { } } - handler_depths_by_action_type.extend( + handler_depths_by_action_id.extend( self.global_actions .keys() .copied() - .map(|action_type| (action_type, contexts.len())), + .map(|action_id| (action_id, 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() { + .filter_map(move |(name, (action_id, deserialize))| { + if let Some(action_depth) = handler_depths_by_action_id.get(action_id).copied() { let action = deserialize(serde_json::Value::Object(Default::default())).ok()?; let bindings = self .keystroke_matcher - .bindings_for_action_type(*type_id) + .bindings_for_action(*action_id) .filter(|b| { action.eq(b.action()) && (0..=action_depth) @@ -434,11 +430,7 @@ impl<'a> WindowContext<'a> { MatchResult::None => false, MatchResult::Pending => true, MatchResult::Matches(matches) => { - let no_action_id = (NoAction {}).id(); for (view_id, action) in matches { - if action.id() == no_action_id { - return false; - } if self.dispatch_action(Some(*view_id), action.as_ref()) { self.keystroke_matcher.clear_pending(); handled_by = Some(action.boxed_clone()); diff --git a/crates/gpui/src/keymap_matcher.rs b/crates/gpui/src/keymap_matcher.rs index bc70638b2c..8cb7d30dfe 100644 --- a/crates/gpui/src/keymap_matcher.rs +++ b/crates/gpui/src/keymap_matcher.rs @@ -8,7 +8,7 @@ use std::{any::TypeId, fmt::Debug}; use collections::HashMap; use smallvec::SmallVec; -use crate::Action; +use crate::{Action, NoAction}; pub use binding::{Binding, BindingMatchResult}; pub use keymap::Keymap; @@ -47,8 +47,8 @@ impl KeymapMatcher { self.keymap.clear(); } - pub fn bindings_for_action_type(&self, action_type: TypeId) -> impl Iterator { - self.keymap.bindings_for_action_type(action_type) + pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator { + self.keymap.bindings_for_action(action_id) } pub fn clear_pending(&mut self) { @@ -81,6 +81,7 @@ impl KeymapMatcher { // The key is the reverse position of the binding in the bindings list so that later bindings // match before earlier ones in the user's config let mut matched_bindings: Vec<(usize, Box)> = Default::default(); + let no_action_id = (NoAction {}).id(); let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); @@ -108,7 +109,9 @@ impl KeymapMatcher { match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { BindingMatchResult::Complete(action) => { - matched_bindings.push((*view_id, action)); + if action.id() != no_action_id { + matched_bindings.push((*view_id, action)); + } } BindingMatchResult::Partial => { self.pending_views diff --git a/crates/gpui/src/keymap_matcher/binding.rs b/crates/gpui/src/keymap_matcher/binding.rs index 4d8334128b..527052c85d 100644 --- a/crates/gpui/src/keymap_matcher/binding.rs +++ b/crates/gpui/src/keymap_matcher/binding.rs @@ -7,8 +7,8 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke}; pub struct Binding { action: Box, - keystrokes: SmallVec<[Keystroke; 2]>, - context_predicate: Option, + pub(super) keystrokes: SmallVec<[Keystroke; 2]>, + pub(super) context_predicate: Option, } impl std::fmt::Debug for Binding { diff --git a/crates/gpui/src/keymap_matcher/keymap.rs b/crates/gpui/src/keymap_matcher/keymap.rs index 6f358aad39..7cb95cab3a 100644 --- a/crates/gpui/src/keymap_matcher/keymap.rs +++ b/crates/gpui/src/keymap_matcher/keymap.rs @@ -1,61 +1,388 @@ +use collections::HashSet; use smallvec::SmallVec; -use std::{ - any::{Any, TypeId}, - collections::HashMap, -}; +use std::{any::TypeId, collections::HashMap}; -use super::Binding; +use crate::{Action, NoAction}; + +use super::{Binding, KeymapContextPredicate, Keystroke}; #[derive(Default)] pub struct Keymap { bindings: Vec, - binding_indices_by_action_type: HashMap>, + binding_indices_by_action_id: HashMap>, + disabled_keystrokes: HashMap, HashSet>>, } impl Keymap { - pub fn new(bindings: Vec) -> Self { - let mut binding_indices_by_action_type = HashMap::new(); - for (ix, binding) in bindings.iter().enumerate() { - binding_indices_by_action_type - .entry(binding.action().type_id()) - .or_insert_with(SmallVec::new) - .push(ix); - } - - Self { - binding_indices_by_action_type, - bindings, - } + #[cfg(test)] + pub(super) fn new(bindings: Vec) -> Self { + let mut this = Self::default(); + this.add_bindings(bindings); + this } - pub(crate) fn bindings_for_action_type( + pub(crate) fn bindings_for_action( &self, - action_type: TypeId, + action_id: TypeId, ) -> impl Iterator { - self.binding_indices_by_action_type - .get(&action_type) + self.binding_indices_by_action_id + .get(&action_id) .map(SmallVec::as_slice) .unwrap_or(&[]) .iter() .map(|ix| &self.bindings[*ix]) + .filter(|binding| !self.binding_disabled(binding)) } pub(crate) fn add_bindings>(&mut self, bindings: T) { + let no_action_id = (NoAction {}).id(); + let mut new_bindings = Vec::new(); + let mut has_new_disabled_keystrokes = false; for binding in bindings { - self.binding_indices_by_action_type - .entry(binding.action().as_any().type_id()) - .or_default() - .push(self.bindings.len()); - self.bindings.push(binding); + if binding.action().id() == no_action_id { + has_new_disabled_keystrokes |= self + .disabled_keystrokes + .entry(binding.keystrokes) + .or_default() + .insert(binding.context_predicate); + } else { + new_bindings.push(binding); + } + } + + if has_new_disabled_keystrokes { + self.binding_indices_by_action_id.retain(|_, indices| { + indices.retain(|ix| { + let binding = &self.bindings[*ix]; + match self.disabled_keystrokes.get(&binding.keystrokes) { + Some(disabled_predicates) => { + !disabled_predicates.contains(&binding.context_predicate) + } + None => true, + } + }); + !indices.is_empty() + }); + } + + for new_binding in new_bindings { + if !self.binding_disabled(&new_binding) { + self.binding_indices_by_action_id + .entry(new_binding.action().id()) + .or_default() + .push(self.bindings.len()); + self.bindings.push(new_binding); + } } } pub(crate) fn clear(&mut self) { self.bindings.clear(); - self.binding_indices_by_action_type.clear(); + self.binding_indices_by_action_id.clear(); + self.disabled_keystrokes.clear(); } - pub fn bindings(&self) -> &Vec { - &self.bindings + pub fn bindings(&self) -> Vec<&Binding> { + self.bindings + .iter() + .filter(|binding| !self.binding_disabled(binding)) + .collect() + } + + fn binding_disabled(&self, binding: &Binding) -> bool { + match self.disabled_keystrokes.get(&binding.keystrokes) { + Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate), + None => false, + } + } +} + +#[cfg(test)] +mod tests { + use crate::actions; + + use super::*; + + actions!( + keymap_test, + [Present1, Present2, Present3, Duplicate, Missing] + ); + + #[test] + fn regular_keymap() { + let present_1 = Binding::new("ctrl-q", Present1 {}, None); + let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane")); + let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor")); + let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None); + let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane")); + let missing = Binding::new("ctrl-r", Missing {}, None); + let all_bindings = [ + &present_1, + &present_2, + &present_3, + &keystroke_duplicate_to_1, + &full_duplicate_to_2, + &missing, + ]; + + let mut keymap = Keymap::default(); + assert_absent(&keymap, &all_bindings); + assert!(keymap.bindings().is_empty()); + + keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]); + assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]); + assert_present( + &keymap, + &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")], + ); + + keymap.add_bindings([ + keystroke_duplicate_to_1.clone(), + full_duplicate_to_2.clone(), + ]); + assert_absent(&keymap, &[&missing]); + assert!( + !keymap.binding_disabled(&keystroke_duplicate_to_1), + "Duplicate binding 1 was added and should not be disabled" + ); + assert!( + !keymap.binding_disabled(&full_duplicate_to_2), + "Duplicate binding 2 was added and should not be disabled" + ); + + assert_eq!( + keymap + .bindings_for_action(keystroke_duplicate_to_1.action().id()) + .map(|binding| &binding.keystrokes) + .flatten() + .collect::>(), + vec![&Keystroke { + ctrl: true, + alt: false, + shift: false, + cmd: false, + function: false, + key: "q".to_string() + }], + "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap" + ); + assert_eq!( + keymap + .bindings_for_action(full_duplicate_to_2.action().id()) + .map(|binding| &binding.keystrokes) + .flatten() + .collect::>(), + vec![ + &Keystroke { + ctrl: true, + alt: false, + shift: false, + cmd: false, + function: false, + key: "w".to_string() + }, + &Keystroke { + ctrl: true, + alt: false, + shift: false, + cmd: false, + function: false, + key: "w".to_string() + } + ], + "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap" + ); + + let updated_bindings = keymap.bindings(); + let expected_updated_bindings = vec![ + &present_1, + &present_2, + &present_3, + &keystroke_duplicate_to_1, + &full_duplicate_to_2, + ]; + assert_eq!( + updated_bindings.len(), + expected_updated_bindings.len(), + "Unexpected updated keymap bindings {updated_bindings:?}" + ); + for (i, expected) in expected_updated_bindings.iter().enumerate() { + let keymap_binding = &updated_bindings[i]; + assert_eq!( + keymap_binding.context_predicate, expected.context_predicate, + "Unexpected context predicate for keymap {i} element: {keymap_binding:?}" + ); + assert_eq!( + keymap_binding.keystrokes, expected.keystrokes, + "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}" + ); + } + + keymap.clear(); + assert_absent(&keymap, &all_bindings); + assert!(keymap.bindings().is_empty()); + } + + #[test] + fn keymap_with_ignored() { + let present_1 = Binding::new("ctrl-q", Present1 {}, None); + let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane")); + let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor")); + let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None); + let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane")); + let ignored_1 = Binding::new("ctrl-q", NoAction {}, None); + let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane")); + let ignored_3_with_other_context = + Binding::new("ctrl-e", NoAction {}, Some("other_context")); + + let mut keymap = Keymap::default(); + + keymap.add_bindings([ + ignored_1.clone(), + ignored_2.clone(), + ignored_3_with_other_context.clone(), + ]); + assert_absent(&keymap, &[&present_3]); + assert_disabled( + &keymap, + &[ + &present_1, + &present_2, + &ignored_1, + &ignored_2, + &ignored_3_with_other_context, + ], + ); + assert!(keymap.bindings().is_empty()); + keymap.clear(); + + keymap.add_bindings([ + present_1.clone(), + present_2.clone(), + present_3.clone(), + ignored_1.clone(), + ignored_2.clone(), + ignored_3_with_other_context.clone(), + ]); + assert_present(&keymap, &[(&present_3, "e")]); + assert_disabled( + &keymap, + &[ + &present_1, + &present_2, + &ignored_1, + &ignored_2, + &ignored_3_with_other_context, + ], + ); + keymap.clear(); + + keymap.add_bindings([ + present_1.clone(), + present_2.clone(), + present_3.clone(), + ignored_1.clone(), + ]); + assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]); + assert_disabled(&keymap, &[&present_1, &ignored_1]); + assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]); + keymap.clear(); + + keymap.add_bindings([ + present_1.clone(), + present_2.clone(), + present_3.clone(), + keystroke_duplicate_to_1.clone(), + full_duplicate_to_2.clone(), + ignored_1.clone(), + ignored_2.clone(), + ignored_3_with_other_context.clone(), + ]); + assert_present(&keymap, &[(&present_3, "e")]); + assert_disabled( + &keymap, + &[ + &present_1, + &present_2, + &keystroke_duplicate_to_1, + &full_duplicate_to_2, + &ignored_1, + &ignored_2, + &ignored_3_with_other_context, + ], + ); + keymap.clear(); + } + + #[track_caller] + fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) { + let keymap_bindings = keymap.bindings(); + assert_eq!( + expected_bindings.len(), + keymap_bindings.len(), + "Unexpected keymap bindings {keymap_bindings:?}" + ); + for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() { + assert!( + !keymap.binding_disabled(expected), + "{expected:?} should not be disabled as it was added into keymap for element {i}" + ); + assert_eq!( + keymap + .bindings_for_action(expected.action().id()) + .map(|binding| &binding.keystrokes) + .flatten() + .collect::>(), + vec![&Keystroke { + ctrl: true, + alt: false, + shift: false, + cmd: false, + function: false, + key: expected_key.to_string() + }], + "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}" + ); + + let keymap_binding = &keymap_bindings[i]; + assert_eq!( + keymap_binding.context_predicate, expected.context_predicate, + "Unexpected context predicate for keymap {i} element: {keymap_binding:?}" + ); + assert_eq!( + keymap_binding.keystrokes, expected.keystrokes, + "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}" + ); + } + } + + #[track_caller] + fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) { + for binding in bindings.iter() { + assert!( + !keymap.binding_disabled(binding), + "{binding:?} should not be disabled in the keymap where was not added" + ); + assert_eq!( + keymap.bindings_for_action(binding.action().id()).count(), + 0, + "{binding:?} should have no actions in the keymap where was not added" + ); + } + } + + #[track_caller] + fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) { + for binding in bindings.iter() { + assert!( + keymap.binding_disabled(binding), + "{binding:?} should be disabled in the keymap" + ); + assert_eq!( + keymap.bindings_for_action(binding.action().id()).count(), + 0, + "{binding:?} should have no actions in the keymap where it was disabled" + ); + } } } diff --git a/crates/gpui/src/keymap_matcher/keymap_context.rs b/crates/gpui/src/keymap_matcher/keymap_context.rs index be61fea531..fd60a8f4b5 100644 --- a/crates/gpui/src/keymap_matcher/keymap_context.rs +++ b/crates/gpui/src/keymap_matcher/keymap_context.rs @@ -44,7 +44,7 @@ impl KeymapContext { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum KeymapContextPredicate { Identifier(String), Equal(String, String), diff --git a/crates/gpui/src/keymap_matcher/keystroke.rs b/crates/gpui/src/keymap_matcher/keystroke.rs index ed3c3f6914..164dea8aba 100644 --- a/crates/gpui/src/keymap_matcher/keystroke.rs +++ b/crates/gpui/src/keymap_matcher/keystroke.rs @@ -3,7 +3,7 @@ use std::fmt::Write; use anyhow::anyhow; use serde::Deserialize; -#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] pub struct Keystroke { pub ctrl: bool, pub alt: bool, diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index e1d80fe25c..509c979b85 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -231,7 +231,7 @@ impl MacForegroundPlatform { } => { // TODO let keystrokes = keystroke_matcher - .bindings_for_action_type(action.as_any().type_id()) + .bindings_for_action(action.id()) .find(|binding| binding.action().eq(action.as_ref())) .map(|binding| binding.keystrokes()); let selector = match os_action { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 2c3c9d5304..31b1133d65 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -2022,6 +2022,9 @@ impl LocalSnapshot { ) -> Vec> { let mut changes = vec![]; let mut edits = vec![]; + + let statuses = repo_ptr.statuses(); + for mut entry in self .descendent_entries(false, false, &work_directory.0) .cloned() @@ -2029,10 +2032,8 @@ impl LocalSnapshot { let Ok(repo_path) = entry.path.strip_prefix(&work_directory.0) else { continue; }; - let git_file_status = repo_ptr - .status(&RepoPath(repo_path.into())) - .log_err() - .flatten(); + let repo_path = RepoPath(repo_path.to_path_buf()); + let git_file_status = statuses.as_ref().and_then(|s| s.get(&repo_path).copied()); if entry.git_status != git_file_status { entry.git_status = git_file_status; changes.push(entry.path.clone()); diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index 8565bc3b56..617667bc9f 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -5,6 +5,7 @@ use parking_lot::Mutex; use serde::Deserialize; use serde_json::Value; use std::{ + borrow::Cow, collections::HashMap, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, @@ -43,7 +44,7 @@ impl ThemeRegistry { this } - pub fn list(&self, staff: bool) -> impl Iterator + '_ { + pub fn list_names(&self, staff: bool) -> impl Iterator> + '_ { let mut dirs = self.assets.list("themes/"); if !staff { @@ -53,10 +54,21 @@ impl ThemeRegistry { .collect() } - dirs.into_iter().filter_map(|path| { - let filename = path.strip_prefix("themes/")?; - let theme_name = filename.strip_suffix(".json")?; - self.get(theme_name).ok().map(|theme| theme.meta.clone()) + fn get_name(path: &str) -> Option<&str> { + path.strip_prefix("themes/")?.strip_suffix(".json") + } + + dirs.into_iter().filter_map(|path| match path { + Cow::Borrowed(path) => Some(Cow::Borrowed(get_name(path)?)), + Cow::Owned(path) => Some(Cow::Owned(get_name(&path)?.to_string())), + }) + } + + pub fn list(&self, staff: bool) -> impl Iterator + '_ { + self.list_names(staff).filter_map(|theme_name| { + self.get(theme_name.as_ref()) + .ok() + .map(|theme| theme.meta.clone()) }) } diff --git a/crates/theme/src/theme_settings.rs b/crates/theme/src/theme_settings.rs index b9e6f7a133..1b53511978 100644 --- a/crates/theme/src/theme_settings.rs +++ b/crates/theme/src/theme_settings.rs @@ -149,8 +149,8 @@ impl settings::Setting for ThemeSettings { let mut root_schema = generator.root_schema_for::(); let theme_names = cx .global::>() - .list(params.staff_mode) - .map(|theme| Value::String(theme.name.clone())) + .list_names(params.staff_mode) + .map(|theme_name| Value::String(theme_name.to_string())) .collect(); let theme_name_schema = SchemaObject { diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index d4d0a3d187..f363f94983 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -106,12 +106,14 @@ impl PickerDelegate for BranchListDelegate { .read_with(&mut cx, |view, cx| { let delegate = view.delegate(); let project = delegate.workspace.read(cx).project().read(&cx); - let mut cwd = - project + + let Some(worktree) = project .visible_worktrees(cx) .next() - .unwrap() - .read(cx) + else { + bail!("Cannot update branch list as there are no visible worktrees") + }; + let mut cwd = worktree .read(cx) .abs_path() .to_path_buf(); cwd.push(".git"); @@ -182,7 +184,9 @@ impl PickerDelegate for BranchListDelegate { fn confirm(&mut self, cx: &mut ViewContext>) { let current_pick = self.selected_index(); - let current_pick = self.matches[current_pick].string.clone(); + let Some(current_pick) = self.matches.get(current_pick).map(|pick| pick.string.clone()) else { + return; + }; cx.spawn(|picker, mut cx| async move { picker .update(&mut cx, |this, cx| { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 151696dc98..ba75656722 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.95.0" +version = "0.95.3" publish = false [lib] diff --git a/crates/zed/RELEASE_CHANNEL b/crates/zed/RELEASE_CHANNEL index 90012116c0..870bbe4e50 100644 --- a/crates/zed/RELEASE_CHANNEL +++ b/crates/zed/RELEASE_CHANNEL @@ -1 +1 @@ -dev \ No newline at end of file +stable \ No newline at end of file diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 09bdbf65be..f7c81b0f1f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -517,11 +517,7 @@ pub fn handle_keymap_file_changes( let mut settings_subscription = None; while let Some(user_keymap_content) = user_keymap_file_rx.next().await { if let Ok(keymap_content) = KeymapFile::parse(&user_keymap_content) { - cx.update(|cx| { - cx.clear_bindings(); - load_default_keymap(cx); - keymap_content.clone().add_to_cx(cx).log_err(); - }); + cx.update(|cx| reload_keymaps(cx, &keymap_content)); let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); drop(settings_subscription); @@ -530,10 +526,7 @@ pub fn handle_keymap_file_changes( let new_base_keymap = *settings::get::(cx); if new_base_keymap != old_base_keymap { old_base_keymap = new_base_keymap.clone(); - - cx.clear_bindings(); - load_default_keymap(cx); - keymap_content.clone().add_to_cx(cx).log_err(); + reload_keymaps(cx, &keymap_content); } }) .detach(); @@ -544,6 +537,13 @@ pub fn handle_keymap_file_changes( .detach(); } +fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) { + cx.clear_bindings(); + load_default_keymap(cx); + keymap_content.clone().add_to_cx(cx).log_err(); + cx.set_menus(menus::menus()); +} + fn open_local_settings_file( workspace: &mut Workspace, _: &OpenLocalSettings,