Compare commits

...
Sign in to create a new pull request.

12 commits

Author SHA1 Message Date
Joseph T. Lyons
33ad93c57d zed 0.95.3 2023-07-21 12:02:37 -04:00
Piotr Osiewicz
26afadc877 branch_list: Bail in case of missing worktrees. (#2726)
Z-2632

Release Notes:
- Fixed a crash that occurred when opening a modal branch picker without
a corktree.
2023-07-21 11:53:27 -04:00
Piotr Osiewicz
b4e6f1b752 branch_list: Ensure index is within list bounds. (#2724)
Z-2630


Release Notes:
- Fixed a crash in branch list that occurred when confirming a match in
empty list.
2023-07-21 11:52:53 -04:00
Joseph T. Lyons
da85199a29 v0.95.x stable 2023-07-19 15:33:38 -04:00
Joseph T. Lyons
2976555c4d Fix ZED_SERVER_URL port number
This change accidentally slipped into https://github.com/zed-industries/zed/pull/2746
2023-07-18 14:14:55 -04:00
Joseph T. Lyons
ab82f1a2c2 Add is_staff to events (#2746)
Release Notes:

- N/A
2023-07-18 14:14:55 -04:00
Max Brunsfeld
585192b040 zed 0.95.2 2023-07-17 11:22:03 -07:00
Max Brunsfeld
ea709a7078 Make macOS application menu aware of which key bindings are disabled (#2735)
Follow-up of https://github.com/zed-industries/zed/pull/2678
Deals with https://github.com/zed-industries/community/issues/772

Refreshes macOs menu panel on keymap file change and properly ignore
disabled actions.

Release Notes:

- Fixes a bug when disabled actions from macOs menu were still working
2023-07-17 11:21:11 -07:00
Max Brunsfeld
45987706c5 Optimize two slow code paths (#2728)
Linear:
https://linear.app/zed-industries/issue/Z-2578/zed-launches-very-slow-for-user

I was searching for the cause of a slow startup time reported in the
above issue, and I don't think I found it, but I did find two very
noticeable slow code paths while profiling, and fixed them.

###  Notes

1. When starting the JSON language server, we provide it with a JSON
schema for our settings. For the `theme` setting, the JSON schema needs
to read all of the themes in the registry, to generate a list of valid
theme names. Previously, as part of this, we were deserializing each
theme from JSON, which took a lot of CPU. Now, we don't do that.
2. When an FS event occurs within a git repository, we reload the git
status for all entries in that git repository. Previously, we did that
via a separate `libgit2` call per FS entry (including ignored entries,
so many thousands in the case of the `zed` repo). Now we do one
`libgit2` call, asking for all of the statuses. Git carries an index of
all of the files with statuses, so this is fast.

Release Notes:

- Improved the the performance of starting up a  JSON language server.
- Improved the performance of handling changes to git repositories, such
as changing branches or committing.
2023-07-17 10:59:55 -07:00
Joseph T. Lyons
5957780724 zed 0.95.1 2023-07-12 16:24:02 -04:00
Joseph T. Lyons
c3e03d8a80 Fix screen sharing panic introduced by call events (#2714)
Release Notes:

- Fixed a bug where Zed would crash when enabling screen share.
2023-07-12 16:23:11 -04:00
Joseph T. Lyons
30ab23c318 v0.95.x preview 2023-07-12 13:30:13 -04:00
20 changed files with 472 additions and 102 deletions

2
Cargo.lock generated
View file

@ -9455,7 +9455,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.95.0"
version = "0.95.3"
dependencies = [
"activity_indicator",
"ai",

View file

@ -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<Client> {
self.client.clone()
}
pub fn pending_invites(&self) -> &HashSet<u64> {
&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<Client>,
cx: &AppContext,
) {
let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call { operation, room_id };
telemetry.report_clickhouse_event(event, telemetry_settings);
}
}

View file

@ -40,6 +40,7 @@ lazy_static! {
struct ClickhouseEventRequestBody {
token: &'static str,
installation_id: Option<Arc<str>>,
is_staff: Option<bool>,
app_version: Option<Arc<str>>,
os_name: &'static str,
os_version: Option<Arc<str>>,
@ -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(),

View file

@ -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<AppState>, 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) {

View file

@ -33,6 +33,7 @@ pub trait GitRepository: Send {
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
fn branches(&self) -> Result<Vec<Branch>> {
Ok(vec![])
}

View file

@ -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())

View file

@ -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<dyn Action>, SmallVec<[Binding; 1]>)> {
let window_id = self.window_id;
let mut contexts = Vec::new();
let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::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());

View file

@ -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<Item = &Binding> {
self.keymap.bindings_for_action_type(action_type)
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
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<dyn Action>)> = 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

View file

@ -7,8 +7,8 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke};
pub struct Binding {
action: Box<dyn Action>,
keystrokes: SmallVec<[Keystroke; 2]>,
context_predicate: Option<KeymapContextPredicate>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<KeymapContextPredicate>,
}
impl std::fmt::Debug for Binding {

View file

@ -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>,
binding_indices_by_action_type: HashMap<TypeId, SmallVec<[usize; 3]>>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
}
impl Keymap {
pub fn new(bindings: Vec<Binding>) -> 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<Binding>) -> 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<Item = &'_ Binding> {
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<T: IntoIterator<Item = Binding>>(&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<Binding> {
&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<_>>(),
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<_>>(),
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<_>>(),
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"
);
}
}
}

View file

@ -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),

View file

@ -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,

View file

@ -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 {

View file

@ -2022,6 +2022,9 @@ impl LocalSnapshot {
) -> Vec<Arc<Path>> {
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());

View file

@ -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<Item = ThemeMeta> + '_ {
pub fn list_names(&self, staff: bool) -> impl Iterator<Item = Cow<str>> + '_ {
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<Item = ThemeMeta> + '_ {
self.list_names(staff).filter_map(|theme_name| {
self.get(theme_name.as_ref())
.ok()
.map(|theme| theme.meta.clone())
})
}

View file

@ -149,8 +149,8 @@ impl settings::Setting for ThemeSettings {
let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
let theme_names = cx
.global::<Arc<ThemeRegistry>>()
.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 {

View file

@ -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<Picker<Self>>) {
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| {

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.95.0"
version = "0.95.3"
publish = false
[lib]

View file

@ -1 +1 @@
dev
stable

View file

@ -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::<BaseKeymap>(cx));
drop(settings_subscription);
@ -530,10 +526,7 @@ pub fn handle_keymap_file_changes(
let new_base_keymap = *settings::get::<BaseKeymap>(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,