From 760fece112cebb311f2c162b33809875be35dd5f Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 19 Jul 2023 15:33:38 -0400 Subject: [PATCH 01/23] v0.96.x preview --- crates/zed/RELEASE_CHANNEL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/RELEASE_CHANNEL b/crates/zed/RELEASE_CHANNEL index 90012116c0..4de2f126df 100644 --- a/crates/zed/RELEASE_CHANNEL +++ b/crates/zed/RELEASE_CHANNEL @@ -1 +1 @@ -dev \ No newline at end of file +preview \ No newline at end of file From 05cd06177c42badf56830f7f2b70c603c9ae34e2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Jul 2023 12:42:30 -0700 Subject: [PATCH 02/23] Mute mics by default (#2754) This adds a setting to mute mics by default. fixes https://github.com/zed-industries/community/issues/1769 Release notes: - Fixed a bug with gutter spacing on files that end on a new significant digit - Added a setting for muting on join, and set it to true by default. --- Cargo.lock | 4 ++ assets/settings/default.json | 5 ++ crates/call/Cargo.toml | 4 ++ crates/call/src/call.rs | 4 ++ crates/call/src/call_settings.rs | 27 +++++++++++ crates/call/src/room.rs | 41 +++++++++++++---- crates/collab_ui/src/collab_titlebar_item.rs | 8 ++-- crates/collab_ui/src/collab_ui.rs | 16 +------ crates/editor/src/element.rs | 2 +- .../Sources/LiveKitBridge/LiveKitBridge.swift | 46 +++++++++++++------ crates/live_kit_client/examples/test_app.rs | 2 +- crates/live_kit_client/src/prod.rs | 32 +++++++++++-- crates/live_kit_client/src/test.rs | 17 ++++++- 13 files changed, 157 insertions(+), 51 deletions(-) create mode 100644 crates/call/src/call_settings.rs diff --git a/Cargo.lock b/Cargo.lock index 5a3161c888..91b165ca68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1052,6 +1052,10 @@ dependencies = [ "media", "postage", "project", + "schemars", + "serde", + "serde_derive", + "serde_json", "settings", "util", ] diff --git a/assets/settings/default.json b/assets/settings/default.json index e1f2d93270..d35049a84d 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -66,6 +66,11 @@ // 3. Draw all invisible symbols: // "all" "show_whitespaces": "selection", + // Settings related to calls in Zed + "calls": { + // Join calls with the microphone muted by default + "mute_on_join": true + }, // Scrollbar related settings "scrollbar": { // When to show the scrollbar in the editor. diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 61f3593247..eb448d8d8d 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -36,6 +36,10 @@ anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true postage.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +serde_derive.workspace = true [dev-dependencies] client = { path = "../client", features = ["test-support"] } diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index cf6dd1799c..3fc76b964d 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -1,9 +1,11 @@ +pub mod call_settings; pub mod participant; pub mod room; use std::sync::Arc; use anyhow::{anyhow, Result}; +use call_settings::CallSettings; use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore}; use collections::HashSet; use futures::{future::Shared, FutureExt}; @@ -19,6 +21,8 @@ pub use participant::ParticipantLocation; pub use room::Room; pub fn init(client: Arc, user_store: ModelHandle, cx: &mut AppContext) { + settings::register::(cx); + let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx)); cx.set_global(active_call); } diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs new file mode 100644 index 0000000000..2808a99617 --- /dev/null +++ b/crates/call/src/call_settings.rs @@ -0,0 +1,27 @@ +use schemars::JsonSchema; +use serde_derive::{Deserialize, Serialize}; +use settings::Setting; + +#[derive(Deserialize, Debug)] +pub struct CallSettings { + pub mute_on_join: bool, +} + +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +pub struct CallSettingsContent { + pub mute_on_join: Option, +} + +impl Setting for CallSettings { + const KEY: Option<&'static str> = Some("calls"); + + type FileContent = CallSettingsContent; + + fn load( + default_value: &Self::FileContent, + user_values: &[&Self::FileContent], + _: &gpui::AppContext, + ) -> anyhow::Result { + Self::load_via_json_merge(default_value, user_values) + } +} diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 87e6faf988..08ac8befc4 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1,4 +1,5 @@ use crate::{ + call_settings::CallSettings, participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack}, IncomingCall, }; @@ -19,7 +20,7 @@ use live_kit_client::{ }; use postage::stream::Stream; use project::Project; -use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration}; +use std::{future::Future, mem, panic::Location, pin::Pin, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -153,8 +154,10 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; - this.update(&mut cx, |this, cx| this.share_microphone(cx)) - .await?; + if !cx.read(|cx| settings::get::(cx).mute_on_join) { + this.update(&mut cx, |this, cx| this.share_microphone(cx)) + .await?; + } anyhow::Ok(()) }) @@ -656,7 +659,7 @@ impl Room { peer_id, projects: participant.projects, location, - muted: false, + muted: true, speaking: false, video_tracks: Default::default(), audio_tracks: Default::default(), @@ -670,6 +673,10 @@ impl Room { live_kit.room.remote_video_tracks(&user.id.to_string()); let audio_tracks = live_kit.room.remote_audio_tracks(&user.id.to_string()); + let publications = live_kit + .room + .remote_audio_track_publications(&user.id.to_string()); + for track in video_tracks { this.remote_video_track_updated( RemoteVideoTrackUpdate::Subscribed(track), @@ -677,9 +684,15 @@ impl Room { ) .log_err(); } - for track in audio_tracks { + + for (track, publication) in + audio_tracks.iter().zip(publications.iter()) + { this.remote_audio_track_updated( - RemoteAudioTrackUpdate::Subscribed(track), + RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + ), cx, ) .log_err(); @@ -819,8 +832,8 @@ impl Room { cx.notify(); } RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { + let mut found = false; for participant in &mut self.remote_participants.values_mut() { - let mut found = false; for track in participant.audio_tracks.values() { if track.sid() == track_id { found = true; @@ -832,16 +845,20 @@ impl Room { break; } } + cx.notify(); } - RemoteAudioTrackUpdate::Subscribed(track) => { + RemoteAudioTrackUpdate::Subscribed(track, publication) => { let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self .remote_participants .get_mut(&user_id) .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; + participant.audio_tracks.insert(track_id.clone(), track); + participant.muted = publication.is_muted(); + cx.emit(Event::RemoteAudioTracksChanged { participant_id: participant.peer_id, }); @@ -1053,7 +1070,7 @@ impl Room { self.live_kit .as_ref() .and_then(|live_kit| match &live_kit.microphone_track { - LocalTrack::None => None, + LocalTrack::None => Some(true), LocalTrack::Pending { muted, .. } => Some(*muted), LocalTrack::Published { muted, .. } => Some(*muted), }) @@ -1070,7 +1087,9 @@ impl Room { self.live_kit.as_ref().map(|live_kit| live_kit.deafened) } + #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { + dbg!(Location::caller()); if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); } else if self.is_sharing_mic() { @@ -1244,6 +1263,10 @@ impl Room { pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { let should_mute = !self.is_muted(); if let Some(live_kit) = self.live_kit.as_mut() { + if matches!(live_kit.microphone_track, LocalTrack::None) { + return Ok(self.share_microphone(cx)); + } + let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; live_kit.muted_by_user = should_mute; diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 6cfc9d8e30..ce8d10d655 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -652,10 +652,10 @@ impl CollabTitlebarItem { let is_muted = room.read(cx).is_muted(); if is_muted { icon = "icons/radix/mic-mute.svg"; - tooltip = "Unmute microphone\nRight click for options"; + tooltip = "Unmute microphone"; } else { icon = "icons/radix/mic.svg"; - tooltip = "Mute microphone\nRight click for options"; + tooltip = "Mute microphone"; } let titlebar = &theme.titlebar; @@ -705,10 +705,10 @@ impl CollabTitlebarItem { let is_deafened = room.read(cx).is_deafened().unwrap_or(false); if is_deafened { icon = "icons/radix/speaker-off.svg"; - tooltip = "Unmute speakers\nRight click for options"; + tooltip = "Unmute speakers"; } else { icon = "icons/radix/speaker-loud.svg"; - tooltip = "Mute speakers\nRight click for options"; + tooltip = "Mute speakers"; } let titlebar = &theme.titlebar; diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 7608fdbfee..dbdeb45573 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -18,13 +18,7 @@ use workspace::AppState; actions!( collab, - [ - ToggleScreenSharing, - ToggleMute, - ToggleDeafen, - LeaveCall, - ShareMicrophone - ] + [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] ); pub fn init(app_state: &Arc, cx: &mut AppContext) { @@ -40,7 +34,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { cx.add_global_action(toggle_screen_sharing); cx.add_global_action(toggle_mute); cx.add_global_action(toggle_deafen); - cx.add_global_action(share_microphone); } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { @@ -85,10 +78,3 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { .log_err(); } } - -pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) { - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - room.update(cx, Room::share_microphone) - .detach_and_log_err(cx) - } -} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4f4aa7477d..4962b08db2 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1311,7 +1311,7 @@ impl EditorElement { } fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext) -> f32 { - let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1; + let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1; let style = &self.style; cx.text_layout_cache() diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index 40d3641db2..5f22acf581 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -6,7 +6,7 @@ import ScreenCaptureKit class LKRoomDelegate: RoomDelegate { var data: UnsafeRawPointer var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void - var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void + var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void @@ -16,7 +16,7 @@ class LKRoomDelegate: RoomDelegate { init( data: UnsafeRawPointer, onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, - onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, @@ -43,7 +43,7 @@ class LKRoomDelegate: RoomDelegate { if track.kind == .video { self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) } else if track.kind == .audio { - self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) + self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque()) } } @@ -52,12 +52,12 @@ class LKRoomDelegate: RoomDelegate { self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted) } } - + func room(_ room: Room, didUpdate speakers: [Participant]) { guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return } self.onActiveSpeakersChanged(self.data, speaker_ids) } - + func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { if track.kind == .video { self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString) @@ -104,7 +104,7 @@ class LKVideoRenderer: NSObject, VideoRenderer { public func LKRoomDelegateCreate( data: UnsafeRawPointer, onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, - onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, @@ -180,39 +180,39 @@ public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawP @_cdecl("LKRoomAudioTracksForRemoteParticipant") public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - + for (_, participant) in room.remoteParticipants { if participant.identity == participantId as String { return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray? } } - + return nil; } @_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant") public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - + for (_, participant) in room.remoteParticipants { if participant.identity == participantId as String { return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray? } } - + return nil; } @_cdecl("LKRoomVideoTracksForRemoteParticipant") public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - + for (_, participant) in room.remoteParticipants { if participant.identity == participantId as String { return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray? } } - + return nil; } @@ -222,7 +222,7 @@ public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer { echoCancellation: true, noiseSuppression: true )) - + return Unmanaged.passRetained(track).toOpaque() } @@ -276,7 +276,7 @@ public func LKLocalTrackPublicationSetMute( callback_data: UnsafeRawPointer ) { let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - + if muted { publication.mute().then { on_complete(callback_data, nil) @@ -307,3 +307,21 @@ public func LKRemoteTrackPublicationSetEnabled( on_complete(callback_data, error.localizedDescription as CFString) } } + +@_cdecl("LKRemoteTrackPublicationIsMuted") +public func LKRemoteTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.muted +} + +@_cdecl("LKRemoteTrackPublicationGetSid") +public func LKRemoteTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index f5f6d0e46f..f2169d7f30 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -63,7 +63,7 @@ fn main() { let audio_track = LocalAudioTrack::create(); let audio_track_publication = room_a.publish_audio_track(&audio_track).await.unwrap(); - if let RemoteAudioTrackUpdate::Subscribed(track) = + if let RemoteAudioTrackUpdate::Subscribed(track, _) = audio_track_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 6daa0601ca..d8d0277440 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -26,6 +26,7 @@ extern "C" { publisher_id: CFStringRef, track_id: CFStringRef, remote_track: *const c_void, + remote_publication: *const c_void, ), on_did_unsubscribe_from_remote_audio_track: extern "C" fn( callback_data: *mut c_void, @@ -125,6 +126,9 @@ extern "C" { on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), callback_data: *mut c_void, ); + + fn LKRemoteTrackPublicationIsMuted(publication: *const c_void) -> bool; + fn LKRemoteTrackPublicationGetSid(publication: *const c_void) -> CFStringRef; } pub type Sid = String; @@ -372,11 +376,19 @@ impl Room { rx } - fn did_subscribe_to_remote_audio_track(&self, track: RemoteAudioTrack) { + fn did_subscribe_to_remote_audio_track( + &self, + track: RemoteAudioTrack, + publication: RemoteTrackPublication, + ) { let track = Arc::new(track); + let publication = Arc::new(publication); self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(track.clone())) - .is_ok() + tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) + .is_ok() }); } @@ -501,13 +513,15 @@ impl RoomDelegate { publisher_id: CFStringRef, track_id: CFStringRef, track: *const c_void, + publication: *const c_void, ) { let room = unsafe { Weak::from_raw(room as *mut Room) }; let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; let track = RemoteAudioTrack::new(track, track_id, publisher_id); + let publication = RemoteTrackPublication::new(publication); if let Some(room) = room.upgrade() { - room.did_subscribe_to_remote_audio_track(track); + room.did_subscribe_to_remote_audio_track(track, publication); } let _ = Weak::into_raw(room); } @@ -682,6 +696,14 @@ impl RemoteTrackPublication { Self(native_track_publication) } + pub fn sid(&self) -> String { + unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } + } + + pub fn is_muted(&self) -> bool { + unsafe { LKRemoteTrackPublicationIsMuted(self.0) } + } + pub fn set_enabled(&self, enabled: bool) -> impl Future> { let (tx, rx) = futures::channel::oneshot::channel(); @@ -832,7 +854,7 @@ pub enum RemoteVideoTrackUpdate { pub enum RemoteAudioTrackUpdate { ActiveSpeakersChanged { speakers: Vec }, MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc), + Subscribed(Arc, Arc), Unsubscribed { publisher_id: Sid, track_id: Sid }, } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index ada864fc44..704760bab7 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -216,6 +216,8 @@ impl TestServer { publisher_id: identity.clone(), }); + let publication = Arc::new(RemoteTrackPublication); + room.audio_tracks.push(track.clone()); for (id, client_room) in &room.client_rooms { @@ -225,7 +227,10 @@ impl TestServer { .lock() .audio_track_updates .0 - .try_broadcast(RemoteAudioTrackUpdate::Subscribed(track.clone())) + .try_broadcast(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) .unwrap(); } } @@ -501,6 +506,14 @@ impl RemoteTrackPublication { pub fn set_enabled(&self, _enabled: bool) -> impl Future> { async { Ok(()) } } + + pub fn is_muted(&self) -> bool { + false + } + + pub fn sid(&self) -> String { + "".to_string() + } } #[derive(Clone)] @@ -579,7 +592,7 @@ pub enum RemoteVideoTrackUpdate { pub enum RemoteAudioTrackUpdate { ActiveSpeakersChanged { speakers: Vec }, MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc), + Subscribed(Arc, Arc), Unsubscribed { publisher_id: Sid, track_id: Sid }, } From 4274ccee621ae249bd4d1861e4d0abe504abf25e Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 19 Jul 2023 16:24:29 -0400 Subject: [PATCH 03/23] Fix return type in watch_file_types() --- crates/zed/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ea019a0fdd..3f89ab3f1f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -717,9 +717,7 @@ async fn watch_languages(_: Arc, _: Arc) -> Option<()> } #[cfg(not(debug_assertions))] -fn watch_file_types(fs: Arc, cx: &mut AppContext) { - None -} +fn watch_file_types(fs: Arc, cx: &mut AppContext) {} fn connect_to_cli( server_name: &str, From 28e04bb4126a4ba0f3ee4ba367b7f257965d544a Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 20 Jul 2023 16:10:20 -0400 Subject: [PATCH 04/23] Add microphone toggle events (#2765) Release Notes: - N/A --- crates/call/src/call.rs | 15 --------------- crates/collab_ui/src/collab_ui.rs | 20 +++++++++++++++++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 3fc76b964d..2defd6b40f 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -284,21 +284,6 @@ impl ActiveCall { } } - pub fn toggle_screen_sharing(&self, cx: &mut AppContext) { - if let Some(room) = self.room().cloned() { - let toggle_screen_sharing = room.update(cx, |room, cx| { - if room.is_screen_sharing() { - self.report_call_event("disable screen share", cx); - Task::ready(room.unshare_screen(cx)) - } else { - self.report_call_event("enable screen share", cx); - room.share_screen(cx) - } - }); - toggle_screen_sharing.detach_and_log_err(cx); - } - } - pub fn share_project( &mut self, project: ModelHandle, diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index dbdeb45573..7d1aa5d1b0 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -64,10 +64,24 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { } pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { + let call = ActiveCall::global(cx).read(cx); if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - room.update(cx, Room::toggle_mute) - .map(|task| task.detach_and_log_err(cx)) - .log_err(); + let client = call.client(); + room.update(cx, |room, cx| { + if room.is_muted() { + ActiveCall::report_call_event_for_room("enable microphone", room.id(), &client, cx); + } else { + ActiveCall::report_call_event_for_room( + "disable microphone", + room.id(), + &client, + cx, + ); + } + room.toggle_mute(cx) + }) + .map(|task| task.detach_and_log_err(cx)) + .log_err(); } } From 04a1a96f8c4dbec2321e00be9c8e34491962f341 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 20 Jul 2023 16:21:21 -0400 Subject: [PATCH 05/23] Reuse previously-obtained call object --- crates/collab_ui/src/collab_ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 7d1aa5d1b0..df4b502391 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -65,7 +65,7 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { let call = ActiveCall::global(cx).read(cx); - if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + if let Some(room) = call.room().cloned() { let client = call.client(); room.update(cx, |room, cx| { if room.is_muted() { From 0472a6ff83966f626cdcd35e72ef3e3cd26b3e7b Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 20 Jul 2023 14:17:29 -0400 Subject: [PATCH 06/23] Add the `local` and `declare` keywords to bash syntax highlighting (#2761) Release Notes: - Improved Bash / Shell Script syntax highlighting --- crates/zed/src/languages/bash/highlights.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/zed/src/languages/bash/highlights.scm b/crates/zed/src/languages/bash/highlights.scm index f33a7c2d3a..a72c5468ed 100644 --- a/crates/zed/src/languages/bash/highlights.scm +++ b/crates/zed/src/languages/bash/highlights.scm @@ -27,6 +27,8 @@ "unset" "until" "while" + "local" + "declare" ] @keyword (comment) @comment From 0be56d6a30e2f9e36d0877392e8c51012ed77eba Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 10:55:19 -0700 Subject: [PATCH 07/23] Add a double click to reset resized splits (#2762) fixes https://github.com/zed-industries/community/issues/1791 Release Notes: - Double clicking on split resize handles now resets the split's dimensions --- crates/workspace/src/pane_group.rs | 112 ++++++++++++++++------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 52761b06c8..0312180597 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -752,63 +752,79 @@ mod element { let child_size = child.size(); let next_child_size = next_child.size(); let drag_bounds = visible_bounds.clone(); - let flexes = self.flexes.clone(); - let current_flex = flexes.borrow()[ix]; + let flexes = self.flexes.borrow(); + let current_flex = flexes[ix]; let next_ix = *next_ix; - let next_flex = flexes.borrow()[next_ix]; + let next_flex = flexes[next_ix]; + drop(flexes); enum ResizeHandle {} let mut mouse_region = MouseRegion::new::( cx.view_id(), self.basis + ix, handle_bounds, ); - mouse_region = mouse_region.on_drag( - MouseButton::Left, - move |drag, workspace: &mut Workspace, cx| { - let min_size = match axis { - Axis::Horizontal => HORIZONTAL_MIN_SIZE, - Axis::Vertical => VERTICAL_MIN_SIZE, - }; - // Don't allow resizing to less than the minimum size, if elements are already too small - if min_size - 1. > child_size.along(axis) - || min_size - 1. > next_child_size.along(axis) - { - return; + mouse_region = mouse_region + .on_drag(MouseButton::Left, { + let flexes = self.flexes.clone(); + move |drag, workspace: &mut Workspace, cx| { + let min_size = match axis { + Axis::Horizontal => HORIZONTAL_MIN_SIZE, + Axis::Vertical => VERTICAL_MIN_SIZE, + }; + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - 1. > child_size.along(axis) + || min_size - 1. > next_child_size.along(axis) + { + return; + } + + let mut current_target_size = + (drag.position - child_start).along(axis); + + let proposed_current_pixel_change = + current_target_size - child_size.along(axis); + + if proposed_current_pixel_change < 0. { + current_target_size = f32::max(current_target_size, min_size); + } else if proposed_current_pixel_change > 0. { + // TODO: cascade this change to other children if current item is at min size + let next_target_size = f32::max( + next_child_size.along(axis) - proposed_current_pixel_change, + min_size, + ); + current_target_size = f32::min( + current_target_size, + child_size.along(axis) + next_child_size.along(axis) + - next_target_size, + ); + } + + let current_pixel_change = + current_target_size - child_size.along(axis); + let flex_change = + current_pixel_change / drag_bounds.length_along(axis); + let current_target_flex = current_flex + flex_change; + let next_target_flex = next_flex - flex_change; + + let mut borrow = flexes.borrow_mut(); + *borrow.get_mut(ix).unwrap() = current_target_flex; + *borrow.get_mut(next_ix).unwrap() = next_target_flex; + + workspace.schedule_serialize(cx); + cx.notify(); } - - let mut current_target_size = (drag.position - child_start).along(axis); - - let proposed_current_pixel_change = - current_target_size - child_size.along(axis); - - if proposed_current_pixel_change < 0. { - current_target_size = f32::max(current_target_size, min_size); - } else if proposed_current_pixel_change > 0. { - // TODO: cascade this change to other children if current item is at min size - let next_target_size = f32::max( - next_child_size.along(axis) - proposed_current_pixel_change, - min_size, - ); - current_target_size = f32::min( - current_target_size, - child_size.along(axis) + next_child_size.along(axis) - - next_target_size, - ); + }) + .on_click(MouseButton::Left, { + let flexes = self.flexes.clone(); + move |e, v: &mut Workspace, cx| { + if e.click_count >= 2 { + let mut borrow = flexes.borrow_mut(); + *borrow = vec![1.; borrow.len()]; + v.schedule_serialize(cx); + cx.notify(); + } } - - let current_pixel_change = current_target_size - child_size.along(axis); - let flex_change = current_pixel_change / drag_bounds.length_along(axis); - let current_target_flex = current_flex + flex_change; - let next_target_flex = next_flex - flex_change; - - let mut borrow = flexes.borrow_mut(); - *borrow.get_mut(ix).unwrap() = current_target_flex; - *borrow.get_mut(next_ix).unwrap() = next_target_flex; - - workspace.schedule_serialize(cx); - cx.notify(); - }, - ); + }); scene.push_mouse_region(mouse_region); scene.pop_stacking_context(); From d080220e9e7b26b57a79cada07e528e347de62d6 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 20 Jul 2023 13:59:21 -0700 Subject: [PATCH 08/23] Folder icons (#2764) - Updates icons and adds more - Adds ability to choose folders or chevrons in user settings - Adds ability to set indent size in user settings --- assets/icons/file_icons/archive.svg | 6 +-- assets/icons/file_icons/audio.svg | 6 +++ assets/icons/file_icons/book.svg | 6 ++- assets/icons/file_icons/camera.svg | 4 +- assets/icons/file_icons/chevron_down.svg | 3 ++ assets/icons/file_icons/chevron_left.svg | 3 ++ assets/icons/file_icons/chevron_right.svg | 3 ++ assets/icons/file_icons/chevron_up.svg | 3 ++ assets/icons/file_icons/code.svg | 4 +- assets/icons/file_icons/database.svg | 2 +- assets/icons/file_icons/eslint.svg | 2 +- assets/icons/file_icons/file.svg | 4 +- assets/icons/file_icons/file_types.json | 26 +++++++--- assets/icons/file_icons/folder-open.svg | 4 -- assets/icons/file_icons/folder.svg | 5 +- assets/icons/file_icons/folder_open.svg | 5 ++ assets/icons/file_icons/git.svg | 2 +- assets/icons/file_icons/image.svg | 7 +-- assets/icons/file_icons/lock.svg | 4 +- assets/icons/file_icons/notebook.svg | 6 ++- assets/icons/file_icons/package.svg | 3 +- assets/icons/file_icons/prettier.svg | 10 ++-- assets/icons/file_icons/rust.svg | 2 +- assets/icons/file_icons/settings.svg | 4 +- assets/icons/file_icons/terminal.svg | 2 +- assets/icons/file_icons/toml.svg | 5 ++ assets/icons/file_icons/typescript.svg | 6 +-- assets/icons/file_icons/video.svg | 4 ++ assets/settings/default.json | 20 ++++---- crates/project_panel/src/file_associations.rs | 25 ++++++++-- crates/project_panel/src/project_panel.rs | 50 ++++++++++--------- .../src/project_panel_settings.rs | 16 +++--- 32 files changed, 163 insertions(+), 89 deletions(-) create mode 100644 assets/icons/file_icons/audio.svg create mode 100644 assets/icons/file_icons/chevron_down.svg create mode 100644 assets/icons/file_icons/chevron_left.svg create mode 100644 assets/icons/file_icons/chevron_right.svg create mode 100644 assets/icons/file_icons/chevron_up.svg delete mode 100644 assets/icons/file_icons/folder-open.svg create mode 100644 assets/icons/file_icons/folder_open.svg create mode 100644 assets/icons/file_icons/toml.svg create mode 100644 assets/icons/file_icons/video.svg diff --git a/assets/icons/file_icons/archive.svg b/assets/icons/file_icons/archive.svg index f11115cdce..820c5846ba 100644 --- a/assets/icons/file_icons/archive.svg +++ b/assets/icons/file_icons/archive.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/assets/icons/file_icons/audio.svg b/assets/icons/file_icons/audio.svg new file mode 100644 index 0000000000..c2275efb63 --- /dev/null +++ b/assets/icons/file_icons/audio.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/file_icons/book.svg b/assets/icons/file_icons/book.svg index 890b8988a3..c9aa764d72 100644 --- a/assets/icons/file_icons/book.svg +++ b/assets/icons/file_icons/book.svg @@ -1,4 +1,6 @@ - - + + + + diff --git a/assets/icons/file_icons/camera.svg b/assets/icons/file_icons/camera.svg index d8b9cf459c..bc1993ad63 100644 --- a/assets/icons/file_icons/camera.svg +++ b/assets/icons/file_icons/camera.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/icons/file_icons/chevron_down.svg b/assets/icons/file_icons/chevron_down.svg new file mode 100644 index 0000000000..b971555cfa --- /dev/null +++ b/assets/icons/file_icons/chevron_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/chevron_left.svg b/assets/icons/file_icons/chevron_left.svg new file mode 100644 index 0000000000..8e61beed5d --- /dev/null +++ b/assets/icons/file_icons/chevron_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/chevron_right.svg b/assets/icons/file_icons/chevron_right.svg new file mode 100644 index 0000000000..fcd9d83fc2 --- /dev/null +++ b/assets/icons/file_icons/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/chevron_up.svg b/assets/icons/file_icons/chevron_up.svg new file mode 100644 index 0000000000..171cdd61c0 --- /dev/null +++ b/assets/icons/file_icons/chevron_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/file_icons/code.svg b/assets/icons/file_icons/code.svg index 2733e4b535..5e59cbe58f 100644 --- a/assets/icons/file_icons/code.svg +++ b/assets/icons/file_icons/code.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/icons/file_icons/database.svg b/assets/icons/file_icons/database.svg index 9072e091b5..812d147717 100644 --- a/assets/icons/file_icons/database.svg +++ b/assets/icons/file_icons/database.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/eslint.svg b/assets/icons/file_icons/eslint.svg index ec5051d447..14ac83df96 100644 --- a/assets/icons/file_icons/eslint.svg +++ b/assets/icons/file_icons/eslint.svg @@ -1,4 +1,4 @@ - + diff --git a/assets/icons/file_icons/file.svg b/assets/icons/file_icons/file.svg index cc422734e7..bfffe03684 100644 --- a/assets/icons/file_icons/file.svg +++ b/assets/icons/file_icons/file.svg @@ -1,5 +1,5 @@ - + - + diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json index 4f3f8160d7..0ccf9c2bb7 100644 --- a/assets/icons/file_icons/file_types.json +++ b/assets/icons/file_icons/file_types.json @@ -17,6 +17,7 @@ "fish": "terminal", "gitattributes": "vcs", "gitignore": "vcs", + "gitmodules": "vcs", "gif": "image", "go": "code", "h": "code", @@ -74,7 +75,7 @@ "svg": "image", "swift": "code", "tiff": "image", - "toml": "settings", + "toml": "toml", "ts": "typescript", "tsx": "code", "txt": "document", @@ -89,25 +90,31 @@ }, "types": { "audio": { - "icon": "icons/file_icons/file.svg" + "icon": "icons/file_icons/audio.svg" }, "code": { "icon": "icons/file_icons/code.svg" }, + "collapsed_chevron": { + "icon": "icons/file_icons/chevron_right.svg" + }, + "collapsed_folder": { + "icon": "icons/file_icons/folder.svg" + }, "default": { "icon": "icons/file_icons/file.svg" }, - "directory": { - "icon": "icons/file_icons/folder.svg" - }, "document": { "icon": "icons/file_icons/book.svg" }, "eslint": { "icon": "icons/file_icons/eslint.svg" }, - "expanded_directory": { - "icon": "icons/file_icons/folder-open.svg" + "expanded_chevron": { + "icon": "icons/file_icons/chevron_down.svg" + }, + "expanded_folder": { + "icon": "icons/file_icons/folder_open.svg" }, "image": { "icon": "icons/file_icons/image.svg" @@ -136,6 +143,9 @@ "terminal": { "icon": "icons/file_icons/terminal.svg" }, + "toml": { + "icon": "icons/file_icons/toml.svg" + }, "typescript": { "icon": "icons/file_icons/typescript.svg" }, @@ -143,7 +153,7 @@ "icon": "icons/file_icons/git.svg" }, "video": { - "icon": "icons/file_icons/file.svg" + "icon": "icons/file_icons/video.svg" } } } diff --git a/assets/icons/file_icons/folder-open.svg b/assets/icons/file_icons/folder-open.svg deleted file mode 100644 index 65c5744049..0000000000 --- a/assets/icons/file_icons/folder-open.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/icons/file_icons/folder.svg b/assets/icons/file_icons/folder.svg index 5157bae839..fd45ab1c44 100644 --- a/assets/icons/file_icons/folder.svg +++ b/assets/icons/file_icons/folder.svg @@ -1,4 +1,5 @@ - - + + + diff --git a/assets/icons/file_icons/folder_open.svg b/assets/icons/file_icons/folder_open.svg new file mode 100644 index 0000000000..bf64f6ee39 --- /dev/null +++ b/assets/icons/file_icons/folder_open.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/git.svg b/assets/icons/file_icons/git.svg index 82d8c8f57c..a30b47fb86 100644 --- a/assets/icons/file_icons/git.svg +++ b/assets/icons/file_icons/git.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/file_icons/image.svg b/assets/icons/file_icons/image.svg index ee5e49f2d4..c258170dae 100644 --- a/assets/icons/file_icons/image.svg +++ b/assets/icons/file_icons/image.svg @@ -1,6 +1,7 @@ - - - + + + + diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg index 3051bbf801..b6aa3394ef 100644 --- a/assets/icons/file_icons/lock.svg +++ b/assets/icons/file_icons/lock.svg @@ -1,6 +1,6 @@ - - + + diff --git a/assets/icons/file_icons/notebook.svg b/assets/icons/file_icons/notebook.svg index 6eaec16d0a..4f55ceac58 100644 --- a/assets/icons/file_icons/notebook.svg +++ b/assets/icons/file_icons/notebook.svg @@ -1,6 +1,8 @@ - + + - + + diff --git a/assets/icons/file_icons/package.svg b/assets/icons/file_icons/package.svg index 2a692ba4b4..a46126e3e9 100644 --- a/assets/icons/file_icons/package.svg +++ b/assets/icons/file_icons/package.svg @@ -1,3 +1,4 @@ - + + diff --git a/assets/icons/file_icons/prettier.svg b/assets/icons/file_icons/prettier.svg index 2d2c6ee719..23cefe0efc 100644 --- a/assets/icons/file_icons/prettier.svg +++ b/assets/icons/file_icons/prettier.svg @@ -1,12 +1,12 @@ - - + + - + - + - + diff --git a/assets/icons/file_icons/rust.svg b/assets/icons/file_icons/rust.svg index 1802f0e190..91982b3eeb 100644 --- a/assets/icons/file_icons/rust.svg +++ b/assets/icons/file_icons/rust.svg @@ -1,4 +1,4 @@ - + diff --git a/assets/icons/file_icons/settings.svg b/assets/icons/file_icons/settings.svg index e827055d19..35af7e1899 100644 --- a/assets/icons/file_icons/settings.svg +++ b/assets/icons/file_icons/settings.svg @@ -1,4 +1,4 @@ - - + + diff --git a/assets/icons/file_icons/terminal.svg b/assets/icons/file_icons/terminal.svg index 939587c53e..15dd705b0b 100644 --- a/assets/icons/file_icons/terminal.svg +++ b/assets/icons/file_icons/terminal.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/toml.svg b/assets/icons/file_icons/toml.svg new file mode 100644 index 0000000000..496c41e755 --- /dev/null +++ b/assets/icons/file_icons/toml.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/file_icons/typescript.svg b/assets/icons/file_icons/typescript.svg index 179b3d8572..f7748a86c4 100644 --- a/assets/icons/file_icons/typescript.svg +++ b/assets/icons/file_icons/typescript.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/assets/icons/file_icons/video.svg b/assets/icons/file_icons/video.svg new file mode 100644 index 0000000000..c7ebf98af6 --- /dev/null +++ b/assets/icons/file_icons/video.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/settings/default.json b/assets/settings/default.json index d35049a84d..6dc573ddb6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,14 +102,18 @@ "show_other_hints": true }, "project_panel": { - // Whether to show the git status in the project panel. - "git_status": true, - // Whether to show file icons in the project panel. - "file_icons": true, + // Default width of the project panel. + "default_width": 240, // Where to dock project panel. Can be 'left' or 'right'. "dock": "left", - // Default width of the project panel. - "default_width": 240 + // Whether to show file icons in the project panel. + "file_icons": true, + // Whether to show folder icons or chevrons for directories in the project panel. + "folder_icons": true, + // Whether to show the git status in the project panel. + "git_status": true, + // Amount of indentation for nested items. + "indent_size": 20 }, "assistant": { // Where to dock the assistant. Can be 'left', 'right' or 'bottom'. @@ -203,9 +207,7 @@ "copilot": { // The set of glob patterns for which copilot should be disabled // in any matching file. - "disabled_globs": [ - ".env" - ] + "disabled_globs": [".env"] }, // Settings specific to journaling "journal": { diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 6e2e373d76..2694fa1697 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -17,8 +17,10 @@ pub struct FileAssociations { types: HashMap, } -const DIRECTORY_TYPE: &'static str = "directory"; -const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_directory"; +const COLLAPSED_DIRECTORY_TYPE: &'static str = "collapsed_folder"; +const EXPANDED_DIRECTORY_TYPE: &'static str = "expanded_folder"; +const COLLAPSED_CHEVRON_TYPE: &'static str = "collapsed_chevron"; +const EXPANDED_CHEVRON_TYPE: &'static str = "expanded_chevron"; pub const FILE_TYPES_ASSET: &'static str = "icons/file_icons/file_types.json"; pub fn init(assets: impl AssetSource, cx: &mut AppContext) { @@ -72,7 +74,24 @@ impl FileAssociations { let key = if expanded { EXPANDED_DIRECTORY_TYPE } else { - DIRECTORY_TYPE + COLLAPSED_DIRECTORY_TYPE + }; + + this.types + .get(key) + .map(|type_config| type_config.icon.clone()) + }) + .unwrap_or_else(|| Arc::from("".to_string())) + } + + pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Arc { + iife!({ + let this = cx.has_global::().then(|| cx.global::())?; + + let key = if expanded { + EXPANDED_CHEVRON_TYPE + } else { + COLLAPSED_CHEVRON_TYPE }; this.types diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d97c47a339..8097f5ecfd 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1176,9 +1176,13 @@ impl ProjectPanel { } let end_ix = range.end.min(ix + visible_worktree_entries.len()); - let (git_status_setting, show_file_icons) = { + let (git_status_setting, show_file_icons, show_folder_icons) = { let settings = settings::get::(cx); - (settings.git_status, settings.file_icons) + ( + settings.git_status, + settings.file_icons, + settings.folder_icons, + ) }; if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) { let snapshot = worktree.read(cx).snapshot(); @@ -1193,10 +1197,22 @@ impl ProjectPanel { for entry in visible_worktree_entries[entry_range].iter() { let status = git_status_setting.then(|| entry.git_status).flatten(); let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok(); - let icon = show_file_icons.then(|| match entry.kind { - EntryKind::File(_) => FileAssociations::get_icon(&entry.path, cx), - _ => FileAssociations::get_folder_icon(is_expanded, cx), - }); + let icon = match entry.kind { + EntryKind::File(_) => { + if show_file_icons { + Some(FileAssociations::get_icon(&entry.path, cx)) + } else { + None + } + } + _ => { + if show_folder_icons { + Some(FileAssociations::get_folder_icon(is_expanded, cx)) + } else { + Some(FileAssociations::get_chevron_icon(is_expanded, cx)) + } + } + }; let mut details = EntryDetails { filename: entry @@ -1258,7 +1274,6 @@ impl ProjectPanel { style: &ProjectPanelEntry, cx: &mut ViewContext, ) -> AnyElement { - let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; let mut filename_text_style = style.text.clone(); @@ -1282,26 +1297,14 @@ impl ProjectPanel { .aligned() .constrained() .with_width(style.icon_size) - } else if kind.is_dir() { - if details.is_expanded { - Svg::new("icons/chevron_down_8.svg").with_color(style.chevron_color) - } else { - Svg::new("icons/chevron_right_8.svg").with_color(style.chevron_color) - } - .constrained() - .with_max_width(style.chevron_size) - .with_max_height(style.chevron_size) - .aligned() - .constrained() - .with_width(style.chevron_size) } else { Empty::new() .constrained() - .with_max_width(style.chevron_size) - .with_max_height(style.chevron_size) + .with_max_width(style.icon_size) + .with_max_height(style.icon_size) .aligned() .constrained() - .with_width(style.chevron_size) + .with_width(style.icon_size) }) .with_child(if show_editor && editor.is_some() { ChildView::new(editor.as_ref().unwrap(), cx) @@ -1337,7 +1340,8 @@ impl ProjectPanel { ) -> AnyElement { let kind = details.kind; let path = details.path.clone(); - let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; + let settings = settings::get::(cx); + let padding = theme.container.padding.left + details.depth as f32 * settings.indent_size; let entry_style = if details.is_cut { &theme.cut_entry diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index f0d60d7f4f..126433e5a3 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -12,18 +12,22 @@ pub enum ProjectPanelDockPosition { #[derive(Deserialize, Debug)] pub struct ProjectPanelSettings { - pub git_status: bool, - pub file_icons: bool, - pub dock: ProjectPanelDockPosition, pub default_width: f32, + pub dock: ProjectPanelDockPosition, + pub file_icons: bool, + pub folder_icons: bool, + pub git_status: bool, + pub indent_size: f32, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct ProjectPanelSettingsContent { - pub git_status: Option, - pub file_icons: Option, - pub dock: Option, pub default_width: Option, + pub dock: Option, + pub file_icons: Option, + pub folder_icons: Option, + pub git_status: Option, + pub indent_size: Option, } impl Setting for ProjectPanelSettings { From 65b565656c70d89caf5f95f6a01e1f133c6521bb Mon Sep 17 00:00:00 2001 From: Derek Briggs Date: Thu, 20 Jul 2023 15:06:50 -0600 Subject: [PATCH 09/23] Icon adjustments (#2766) Icon tweaks --- assets/icons/file_icons/archive.svg | 2 +- assets/icons/file_icons/folder_open.svg | 2 +- assets/icons/file_icons/image.svg | 2 +- assets/icons/file_icons/lock.svg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/icons/file_icons/archive.svg b/assets/icons/file_icons/archive.svg index 820c5846ba..35e3dc59bd 100644 --- a/assets/icons/file_icons/archive.svg +++ b/assets/icons/file_icons/archive.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/folder_open.svg b/assets/icons/file_icons/folder_open.svg index bf64f6ee39..55c7d51649 100644 --- a/assets/icons/file_icons/folder_open.svg +++ b/assets/icons/file_icons/folder_open.svg @@ -1,5 +1,5 @@ - + diff --git a/assets/icons/file_icons/image.svg b/assets/icons/file_icons/image.svg index c258170dae..d9d5b82af1 100644 --- a/assets/icons/file_icons/image.svg +++ b/assets/icons/file_icons/image.svg @@ -1,6 +1,6 @@ - + diff --git a/assets/icons/file_icons/lock.svg b/assets/icons/file_icons/lock.svg index b6aa3394ef..14fed3941a 100644 --- a/assets/icons/file_icons/lock.svg +++ b/assets/icons/file_icons/lock.svg @@ -1,6 +1,6 @@ - + From 3722de45065036224c18785e18638dd3a4bfe887 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 20 Jul 2023 17:08:19 -0400 Subject: [PATCH 10/23] zed 0.96.1 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91b165ca68..25c7dd32a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9454,7 +9454,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.96.0" +version = "0.96.1" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index f749fb6e68..42f8ab4879 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.96.0" +version = "0.96.1" publish = false [lib] From e65ddbc11c7443796f6c6732467b36392b8b338c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 21 Jul 2023 11:57:29 +0300 Subject: [PATCH 11/23] Do not highlight fake URLs in terminal (#2770) Closes https://github.com/zed-industries/community/issues/1794 See also https://github.com/alacritty/alacritty/pull/7101 Release Notes: - Fixed terminal incorrectly highlighting certain strings as URLs --- crates/terminal/src/terminal.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 3a64cff24f..f81af1319e 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -74,7 +74,7 @@ const DEBUG_LINE_HEIGHT: f32 = 5.; lazy_static! { // Regex Copied from alacritty's ui_config.rs - static ref URL_REGEX: RegexSearch = RegexSearch::new("(ipfs:|ipns:|magnet:|mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+").unwrap(); + static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap(); static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap(); } @@ -875,8 +875,10 @@ impl Terminal { } else if let Some(word_match) = regex_match_at(term, point, &WORD_REGEX) { let maybe_url_or_path = term.bounds_to_string(*word_match.start(), *word_match.end()); - let is_url = regex_match_at(term, point, &URL_REGEX).is_some(); - + let is_url = match regex_match_at(term, point, &URL_REGEX) { + Some(url_match) => url_match == word_match, + None => false, + }; Some((maybe_url_or_path, is_url, word_match)) } else { None From 8213c3328e3ce20d28f779fccf1a4753170d32f6 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Jul 2023 20:53:31 -0600 Subject: [PATCH 12/23] Fix enter in search (#2768) Fixes a regression in non-vim search caused by my changes to vim search. Release Notes: - N/A --- assets/keymaps/vim.json | 2 +- crates/vim/src/editor_events.rs | 4 ++-- crates/vim/src/normal/search.rs | 15 +++++++++++++++ crates/vim/src/state.rs | 1 + crates/vim/src/vim.rs | 21 ++++++++++++++++----- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 2c406e3eb0..475d4beda9 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -356,7 +356,7 @@ } }, { - "context": "BufferSearchBar", + "context": "BufferSearchBar > VimEnabled", "bindings": { "enter": "vim::SearchSubmit", "escape": "buffer_search::Dismiss" diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index a11f1cc182..60e63f9823 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -13,7 +13,7 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) { cx.update_window(previously_active_editor.window_id(), |cx| { Vim::update(cx, |vim, cx| { vim.update_active_editor(cx, |previously_active_editor, cx| { - Vim::unhook_vim_settings(previously_active_editor, cx); + vim.unhook_vim_settings(previously_active_editor, cx) }); }); }); @@ -35,7 +35,7 @@ fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) { } } - editor.update(cx, |editor, cx| Vim::unhook_vim_settings(editor, cx)) + editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx)) }); }); } diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index cae64a40a6..1e3a64c3a5 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -282,4 +282,19 @@ mod test { cx.simulate_keystrokes(["enter"]); cx.assert_state("aa\nˇbb\ndd\ncc\nbb\n", Mode::Normal); } + + #[gpui::test] + async fn test_non_vim_search( + cx: &mut gpui::TestAppContext, + deterministic: Arc, + ) { + let mut cx = VimTestContext::new(cx, false).await; + cx.set_state("ˇone one one one", Mode::Normal); + cx.simulate_keystrokes(["cmd-f"]); + deterministic.run_until_parked(); + + cx.assert_editor_state("«oneˇ» one one one"); + cx.simulate_keystrokes(["enter"]); + cx.assert_editor_state("one «oneˇ» one one"); + } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 6434b710b2..23471066cd 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -91,6 +91,7 @@ impl VimState { pub fn keymap_context_layer(&self) -> KeymapContext { let mut context = KeymapContext::default(); + context.add_identifier("VimEnabled"); context.add_key( "vim_mode", match self.mode { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index ada8f2c1de..69b94428dd 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -14,8 +14,8 @@ use anyhow::Result; use collections::CommandPaletteFilter; use editor::{Bias, Editor, EditorMode, Event}; use gpui::{ - actions, impl_actions, AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + actions, impl_actions, keymap_matcher::KeymapContext, AppContext, Subscription, ViewContext, + ViewHandle, WeakViewHandle, WindowContext, }; use language::CursorShape; use motion::Motion; @@ -304,17 +304,28 @@ impl Vim { // Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur, // but we need collapse_matches to persist when the search bar is focused. editor.set_collapse_matches(false); - Self::unhook_vim_settings(editor, cx); + self.unhook_vim_settings(editor, cx); } }); } - fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext) { + fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext) { editor.set_cursor_shape(CursorShape::Bar, cx); editor.set_clip_at_line_ends(false, cx); editor.set_input_enabled(true); editor.selections.line_mode = false; - editor.remove_keymap_context_layer::(cx); + + // we set the VimEnabled context on all editors so that we + // can distinguish between vim mode and non-vim mode in the BufferSearchBar. + // This is a bit of a hack, but currently the search crate does not depend on vim, + // and it seems nice to keep it that way. + if self.enabled { + let mut context = KeymapContext::default(); + context.add_identifier("VimEnabled"); + editor.set_keymap_context_layer::(context, cx) + } else { + editor.remove_keymap_context_layer::(cx); + } } } From 61bb05b978a31501a439da3ffcc2f90fe76aad58 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 21 Jul 2023 09:23:04 -0600 Subject: [PATCH 13/23] Fix shift-enter in search (#2772) Fixes shift-enter to go to previous result. Release Notes: - To type a newline in search use `ctrl-enter` (or `ctrl-shift-enter` for a newline below). --- assets/keymaps/default.json | 4 ++-- crates/vim/src/normal/search.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1a13d8cdb3..2aea16d398 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -195,8 +195,8 @@ { "context": "Editor && mode == auto_height", "bindings": { - "shift-enter": "editor::Newline", - "cmd-shift-enter": "editor::NewlineBelow" + "ctrl-enter": "editor::Newline", + "ctrl-shift-enter": "editor::NewlineBelow" } }, { diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 1e3a64c3a5..d584c575d2 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -296,5 +296,7 @@ mod test { cx.assert_editor_state("«oneˇ» one one one"); cx.simulate_keystrokes(["enter"]); cx.assert_editor_state("one «oneˇ» one one"); + cx.simulate_keystrokes(["shift-enter"]); + cx.assert_editor_state("«oneˇ» one one one"); } } From a4132b939fde4e530be37a8cf8ba9212afc027c4 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 21 Jul 2023 12:03:41 -0400 Subject: [PATCH 14/23] zed 0.96.2 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25c7dd32a8..a7e517cffa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9454,7 +9454,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.96.1" +version = "0.96.2" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 42f8ab4879..24a6ad9a6e 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.96.1" +version = "0.96.2" publish = false [lib] From 9de99a703600fcfd1ea2b5f8cb98c73107006d85 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 23 Jul 2023 00:23:19 +0300 Subject: [PATCH 15/23] In terminal, open paths starting with ~ and focus on project panel when opening directories (#2780) Further improves terminal navigation with cmd+click, now allowing to open paths starting with `~` (if they are present otherwise) and focusing project panel with highlighted entry for the directories opened. Release Notes: - Further improves terminal navigation with cmd+click, now allowing to open paths starting with `~` (if they are present otherwise) and focusing project panel with highlighted entry for the directories opened. --- crates/project/src/project.rs | 1 + crates/project_panel/src/project_panel.rs | 9 ++- crates/terminal/src/terminal.rs | 6 +- crates/terminal_view/src/terminal_view.rs | 81 +++++++++++++------ crates/workspace/src/workspace.rs | 98 +++++++++++++++-------- crates/zed/src/zed.rs | 70 +++++++++++++--- 6 files changed, 193 insertions(+), 72 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b3255df812..6b905a1faa 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -259,6 +259,7 @@ pub enum Event { LanguageServerLog(LanguageServerId, String), Notification(String), ActiveEntryChanged(Option), + ActivateProjectPanel, WorktreeAdded, WorktreeRemoved(WorktreeId), WorktreeUpdatedEntries(WorktreeId, UpdatedEntriesSet), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8097f5ecfd..82f9170a9e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -169,6 +169,7 @@ pub enum Event { }, DockPositionChanged, Focus, + ActivatePanel, } #[derive(Serialize, Deserialize)] @@ -195,6 +196,9 @@ impl ProjectPanel { cx.notify(); } } + project::Event::ActivateProjectPanel => { + cx.emit(Event::ActivatePanel); + } project::Event::WorktreeRemoved(id) => { this.expanded_dir_ids.remove(id); this.update_visible_entries(None, cx); @@ -982,7 +986,10 @@ impl ProjectPanel { None } - fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> { + pub fn selected_entry<'a>( + &self, + cx: &'a AppContext, + ) -> Option<(&'a Worktree, &'a project::Entry)> { let (worktree, entry) = self.selected_entry_handle(cx)?; Some((worktree.read(cx), entry)) } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index f81af1319e..e3109102d1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -73,10 +73,12 @@ const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_LINE_HEIGHT: f32 = 5.; lazy_static! { - // Regex Copied from alacritty's ui_config.rs + // Regex Copied from alacritty's ui_config.rs and modified its declaration slightly: + // * avoid Rust-specific escaping. + // * use more strict regex for `file://` protocol matching: original regex has `file:` inside, but we want to avoid matching `some::file::module` strings. static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap(); - static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-]+").unwrap(); + static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-~]+").unwrap(); } ///Upward flowing events, for changing the title and such diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index cdb1d40efc..e108a05ccc 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -187,37 +187,56 @@ impl TerminalView { } let potential_abs_paths = possible_open_targets(&workspace, maybe_path, cx); if let Some(path) = potential_abs_paths.into_iter().next() { - let visible = path.path_like.is_dir(); + let is_dir = path.path_like.is_dir(); let task_workspace = workspace.clone(); cx.spawn(|_, mut cx| async move { - let opened_item = task_workspace + let opened_items = task_workspace .update(&mut cx, |workspace, cx| { - workspace.open_abs_path(path.path_like, visible, cx) + workspace.open_paths(vec![path.path_like], is_dir, cx) }) .context("workspace update")? - .await - .context("workspace update")?; - if let Some(row) = path.row { - let col = path.column.unwrap_or(0); - if let Some(active_editor) = opened_item.downcast::() { - active_editor - .downgrade() - .update(&mut cx, |editor, cx| { - let snapshot = editor.snapshot(cx).display_snapshot; - let point = snapshot.buffer_snapshot.clip_point( - language::Point::new( - row.saturating_sub(1), - col.saturating_sub(1), - ), - Bias::Left, - ); - editor.change_selections( - Some(Autoscroll::center()), - cx, - |s| s.select_ranges([point..point]), - ); - }) - .log_err(); + .await; + anyhow::ensure!( + opened_items.len() == 1, + "For a single path open, expected single opened item" + ); + let opened_item = opened_items + .into_iter() + .next() + .unwrap() + .transpose() + .context("path open")?; + if is_dir { + task_workspace.update(&mut cx, |workspace, cx| { + workspace.project().update(cx, |_, cx| { + cx.emit(project::Event::ActivateProjectPanel); + }) + })?; + } else { + if let Some(row) = path.row { + let col = path.column.unwrap_or(0); + if let Some(active_editor) = + opened_item.and_then(|item| item.downcast::()) + { + active_editor + .downgrade() + .update(&mut cx, |editor, cx| { + let snapshot = editor.snapshot(cx).display_snapshot; + let point = snapshot.buffer_snapshot.clip_point( + language::Point::new( + row.saturating_sub(1), + col.saturating_sub(1), + ), + Bias::Left, + ); + editor.change_selections( + Some(Autoscroll::center()), + cx, + |s| s.select_ranges([point..point]), + ); + }) + .log_err(); + } } } anyhow::Ok(()) @@ -425,6 +444,16 @@ fn possible_open_targets( let maybe_path = path_like.path_like; let potential_abs_paths = if maybe_path.is_absolute() { vec![maybe_path] + } else if maybe_path.starts_with("~") { + if let Some(abs_path) = maybe_path + .strip_prefix("~") + .ok() + .and_then(|maybe_path| Some(dirs::home_dir()?.join(maybe_path))) + { + vec![abs_path] + } else { + Vec::new() + } } else if let Some(workspace) = workspace.upgrade(cx) { workspace.update(cx, |workspace, cx| { workspace diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3e62af8ea6..b5c54e5eb7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -884,6 +884,18 @@ impl Workspace { pub fn add_panel(&mut self, panel: ViewHandle, cx: &mut ViewContext) where T::Event: std::fmt::Debug, + { + self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {}) + } + + pub fn add_panel_with_extra_event_handler( + &mut self, + panel: ViewHandle, + cx: &mut ViewContext, + handler: F, + ) where + T::Event: std::fmt::Debug, + F: Fn(&mut Self, &ViewHandle, &T::Event, &mut ViewContext) + 'static, { let dock = match panel.position(cx) { DockPosition::Left => &self.left_dock, @@ -951,6 +963,8 @@ impl Workspace { } this.update_active_view_for_followers(cx); cx.notify(); + } else { + handler(this, &panel, event, cx) } } })); @@ -1403,45 +1417,65 @@ impl Workspace { // Sort the paths to ensure we add worktrees for parents before their children. abs_paths.sort_unstable(); cx.spawn(|this, mut cx| async move { - let mut project_paths = Vec::new(); - for path in &abs_paths { - if let Some(project_path) = this + let mut tasks = Vec::with_capacity(abs_paths.len()); + for abs_path in &abs_paths { + let project_path = match this .update(&mut cx, |this, cx| { - Workspace::project_path_for_path(this.project.clone(), path, visible, cx) + Workspace::project_path_for_path( + this.project.clone(), + abs_path, + visible, + cx, + ) }) .log_err() { - project_paths.push(project_path.await.log_err()); - } else { - project_paths.push(None); - } - } + Some(project_path) => project_path.await.log_err(), + None => None, + }; - let tasks = abs_paths - .iter() - .cloned() - .zip(project_paths.into_iter()) - .map(|(abs_path, project_path)| { - let this = this.clone(); - cx.spawn(|mut cx| { - let fs = fs.clone(); - async move { - let (_worktree, project_path) = project_path?; - if fs.is_file(&abs_path).await { - Some( - this.update(&mut cx, |this, cx| { - this.open_path(project_path, None, true, cx) + let this = this.clone(); + let task = cx.spawn(|mut cx| { + let fs = fs.clone(); + let abs_path = abs_path.clone(); + async move { + let (worktree, project_path) = project_path?; + if fs.is_file(&abs_path).await { + Some( + this.update(&mut cx, |this, cx| { + this.open_path(project_path, None, true, cx) + }) + .log_err()? + .await, + ) + } else { + this.update(&mut cx, |workspace, cx| { + let worktree = worktree.read(cx); + let worktree_abs_path = worktree.abs_path(); + let entry_id = if abs_path == worktree_abs_path.as_ref() { + worktree.root_entry() + } else { + abs_path + .strip_prefix(worktree_abs_path.as_ref()) + .ok() + .and_then(|relative_path| { + worktree.entry_for_path(relative_path) + }) + } + .map(|entry| entry.id); + if let Some(entry_id) = entry_id { + workspace.project().update(cx, |_, cx| { + cx.emit(project::Event::ActiveEntryChanged(Some(entry_id))); }) - .log_err()? - .await, - ) - } else { - None - } + } + }) + .log_err()?; + None } - }) - }) - .collect::>(); + } + }); + tasks.push(task); + } futures::future::join_all(tasks).await }) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6bbba0bd02..f3f08ee111 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -338,9 +338,19 @@ pub fn initialize_workspace( let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone()); let (project_panel, terminal_panel, assistant_panel) = futures::try_join!(project_panel, terminal_panel, assistant_panel)?; + workspace_handle.update(&mut cx, |workspace, cx| { let project_panel_position = project_panel.position(cx); - workspace.add_panel(project_panel, cx); + workspace.add_panel_with_extra_event_handler( + project_panel, + cx, + |workspace, _, event, cx| match event { + project_panel::Event::ActivatePanel => { + workspace.focus_panel::(cx); + } + _ => {} + }, + ); workspace.add_panel(terminal_panel, cx); workspace.add_panel(assistant_panel, cx); @@ -1085,8 +1095,46 @@ mod tests { ) .await; - let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx)) + .await + .unwrap(); + assert_eq!(cx.window_ids().len(), 1); + let workspace = cx + .read_window(cx.window_ids()[0], |cx| cx.root_view().clone()) + .unwrap() + .downcast::() + .unwrap(); + + #[track_caller] + fn assert_project_panel_selection( + workspace: &Workspace, + expected_worktree_path: &Path, + expected_entry_path: &Path, + cx: &AppContext, + ) { + let project_panel = [ + workspace.left_dock().read(cx).panel::(), + workspace.right_dock().read(cx).panel::(), + workspace.bottom_dock().read(cx).panel::(), + ] + .into_iter() + .find_map(std::convert::identity) + .expect("found no project panels") + .read(cx); + let (selected_worktree, selected_entry) = project_panel + .selected_entry(cx) + .expect("project panel should have a selected entry"); + assert_eq!( + selected_worktree.abs_path().as_ref(), + expected_worktree_path, + "Unexpected project panel selected worktree path" + ); + assert_eq!( + selected_entry.path.as_ref(), + expected_entry_path, + "Unexpected project panel selected entry path" + ); + } // Open a file within an existing worktree. workspace @@ -1095,9 +1143,10 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx); assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() @@ -1118,8 +1167,9 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx); let worktree_roots = workspace - .read(cx) .worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1132,7 +1182,6 @@ mod tests { ); assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() @@ -1153,8 +1202,9 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx); let worktree_roots = workspace - .read(cx) .worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1167,7 +1217,6 @@ mod tests { ); assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() @@ -1188,8 +1237,9 @@ mod tests { }) .await; cx.read(|cx| { + let workspace = workspace.read(cx); + assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx); let worktree_roots = workspace - .read(cx) .worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1202,7 +1252,6 @@ mod tests { ); let visible_worktree_roots = workspace - .read(cx) .visible_worktrees(cx) .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref()) .collect::>(); @@ -1216,7 +1265,6 @@ mod tests { assert_eq!( workspace - .read(cx) .active_pane() .read(cx) .active_item() From 3558e65364d41724aedaa7998a77f4663d54e436 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 24 Jul 2023 15:48:45 +0300 Subject: [PATCH 16/23] Fixes a crash when SelectAllMatches action was called on no matches (#2783) Release Notes: - Fixes a crash when SelectAllMatches action was called on no matches --- crates/search/src/buffer_search.rs | 81 ++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7fade13a50..5429305098 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -568,7 +568,7 @@ impl BufferSearchBar { } fn select_all_matches(&mut self, _: &SelectAllMatches, cx: &mut ViewContext) { - if !self.dismissed { + if !self.dismissed && self.active_match_index.is_some() { if let Some(searchable_item) = self.active_searchable_item.as_ref() { if let Some(matches) = self .searchable_items_with_matches @@ -1175,9 +1175,16 @@ mod tests { .await .unwrap(); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.activate_current_match(cx); }); + cx.read_window(window_id, |cx| { + assert!( + !editor.is_focused(cx), + "Initially, the editor should not be focused" + ); + }); let initial_selections = editor.update(cx, |editor, cx| { let initial_selections = editor.selections.display_ranges(cx); assert_eq!( @@ -1191,7 +1198,16 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should focus editor after successful SelectAllMatches" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1199,8 +1215,6 @@ mod tests { expected_query_matches_count, "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(0), @@ -1210,6 +1224,14 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_next_match(&SelectNextMatch, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should still have editor focused after SelectNextMatch" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1221,8 +1243,6 @@ mod tests { all_selections, initial_selections, "Next match should be different from the first selection" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(1), @@ -1231,7 +1251,16 @@ mod tests { }); search_bar.update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should focus editor after successful SelectAllMatches" + ); + }); + search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1239,8 +1268,6 @@ mod tests { expected_query_matches_count, "Should select all `a` characters in the buffer, but got: {all_selections:?}" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(1), @@ -1250,6 +1277,14 @@ mod tests { search_bar.update(cx, |search_bar, cx| { search_bar.select_prev_match(&SelectPrevMatch, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + editor.is_focused(cx), + "Should still have editor focused after SelectPrevMatch" + ); + }); + let last_match_selections = search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); assert_eq!( @@ -1261,13 +1296,41 @@ mod tests { all_selections, initial_selections, "Previous match should be the same as the first selection" ); - }); - search_bar.update(cx, |search_bar, _| { assert_eq!( search_bar.active_match_index, Some(0), "Match index should be updated to the previous one" ); + all_selections + }); + + search_bar + .update(cx, |search_bar, cx| { + cx.focus(search_bar.query_editor.as_any()); + search_bar.search("abas_nonexistent_match", None, cx) + }) + .await + .unwrap(); + search_bar.update(cx, |search_bar, cx| { + search_bar.select_all_matches(&SelectAllMatches, cx); + }); + cx.read_window(window_id, |cx| { + assert!( + !editor.is_focused(cx), + "Should not switch focus to editor if SelectAllMatches does not find any matches" + ); + }); + search_bar.update(cx, |search_bar, cx| { + let all_selections = + editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); + assert_eq!( + all_selections, last_match_selections, + "Should not select anything new if there are no matches" + ); + assert!( + search_bar.active_match_index.is_none(), + "For no matches, there should be no active match index" + ); }); } } From 713d3ee8ba429eea8708d51225a5fb66b9b797fa Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 26 Jul 2023 09:49:27 -0700 Subject: [PATCH 17/23] Simple cascading split (#2790) This PR cascades the split resizing to adjacent splits, if the current split has already hit the minimum size. This PR also adds support for detecting the end of a drag event to GPUI, via a bool on the dispatched drag. Release Notes: - Made split resizing more flexible --- crates/gpui/src/app/window.rs | 20 ++++ crates/gpui/src/scene/mouse_event.rs | 1 + crates/workspace/src/pane_group.rs | 169 +++++++++++++++++---------- 3 files changed, 126 insertions(+), 64 deletions(-) diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 1dc88d2e71..e4beb58873 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -518,6 +518,18 @@ impl<'a> WindowContext<'a> { // NOTE: The order of event pushes is important! MouseUp events MUST be fired // before click events, and so the MouseUp events need to be pushed before // MouseClick events. + + // Synthesize one last drag event to end the drag + mouse_events.push(MouseEvent::Drag(MouseDrag { + region: Default::default(), + prev_mouse_position: self.window.mouse_position, + platform_event: MouseMovedEvent { + position: e.position, + pressed_button: Some(e.button), + modifiers: e.modifiers, + }, + end: true, + })); mouse_events.push(MouseEvent::Up(MouseUp { region: Default::default(), platform_event: e.clone(), @@ -565,8 +577,16 @@ impl<'a> WindowContext<'a> { region: Default::default(), prev_mouse_position: self.window.mouse_position, platform_event: e.clone(), + end: false, })); } else if let Some((_, clicked_button)) = self.window.clicked_region { + mouse_events.push(MouseEvent::Drag(MouseDrag { + region: Default::default(), + prev_mouse_position: self.window.mouse_position, + platform_event: e.clone(), + end: true, + })); + // Mouse up event happened outside the current window. Simulate mouse up button event let button_event = e.to_button_event(clicked_button); mouse_events.push(MouseEvent::Up(MouseUp { diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index a492da771b..89bf874583 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -32,6 +32,7 @@ pub struct MouseDrag { pub region: RectF, pub prev_mouse_position: Vector2F, pub platform_event: MouseMovedEvent, + pub end: bool, } impl Deref for MouseDrag { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 0312180597..017cdfe1ec 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -522,7 +522,7 @@ impl SplitDirection { } mod element { - use std::{cell::RefCell, ops::Range, rc::Rc}; + use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; use gpui::{ geometry::{ @@ -531,8 +531,9 @@ mod element { }, json::{self, ToJson}, platform::{CursorStyle, MouseButton}, - AnyElement, Axis, CursorRegion, Element, LayoutContext, MouseRegion, RectFExt, - SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, + scene::MouseDrag, + AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion, + RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext, }; use crate::{ @@ -613,6 +614,96 @@ mod element { *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); } } + + fn handle_resize( + flexes: Rc>>, + axis: Axis, + preceding_ix: usize, + child_start: Vector2F, + drag_bounds: RectF, + ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { + let size = move |ix, flexes: &[f32]| { + drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) + }; + + move |drag, workspace: &mut Workspace, cx| { + if drag.end { + // TODO: Clear cascading resize state + return; + } + let min_size = match axis { + Axis::Horizontal => HORIZONTAL_MIN_SIZE, + Axis::Vertical => VERTICAL_MIN_SIZE, + }; + let mut flexes = flexes.borrow_mut(); + + // Don't allow resizing to less than the minimum size, if elements are already too small + if min_size - 1. > size(preceding_ix, flexes.as_slice()) { + return; + } + + let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) + - size(preceding_ix, flexes.as_slice()); + + let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { + let flex_change = pixel_dx / drag_bounds.length_along(axis); + let current_target_flex = flexes[target_ix] + flex_change; + let next_target_flex = + flexes[(target_ix as isize + next) as usize] - flex_change; + (current_target_flex, next_target_flex) + }; + + let mut successors = from_fn({ + let forward = proposed_current_pixel_change > 0.; + let mut ix_offset = 0; + let len = flexes.len(); + move || { + let result = if forward { + (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) + } else { + (preceding_ix as isize - ix_offset as isize >= 0) + .then(|| preceding_ix - ix_offset) + }; + + ix_offset += 1; + + result + } + }); + + while proposed_current_pixel_change.abs() > 0. { + let Some(current_ix) = successors.next() else { + break; + }; + + let next_target_size = f32::max( + size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, + min_size, + ); + + let current_target_size = f32::max( + size(current_ix, flexes.as_slice()) + + size(current_ix + 1, flexes.as_slice()) + - next_target_size, + min_size, + ); + + let current_pixel_change = + current_target_size - size(current_ix, flexes.as_slice()); + + let (current_target_flex, next_target_flex) = + flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); + + flexes[current_ix] = current_target_flex; + flexes[current_ix + 1] = next_target_flex; + + proposed_current_pixel_change -= current_pixel_change; + } + + workspace.schedule_serialize(cx); + cx.notify(); + } + } } impl Extend> for PaneAxisElement { @@ -718,8 +809,7 @@ mod element { Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), } - if let Some(Some((next_ix, next_child))) = can_resize.then(|| children_iter.peek()) - { + if can_resize && children_iter.peek().is_some() { scene.push_stacking_context(None, None); let handle_origin = match self.axis { @@ -748,15 +838,6 @@ mod element { style, }); - let axis = self.axis; - let child_size = child.size(); - let next_child_size = next_child.size(); - let drag_bounds = visible_bounds.clone(); - let flexes = self.flexes.borrow(); - let current_flex = flexes[ix]; - let next_ix = *next_ix; - let next_flex = flexes[next_ix]; - drop(flexes); enum ResizeHandle {} let mut mouse_region = MouseRegion::new::( cx.view_id(), @@ -764,56 +845,16 @@ mod element { handle_bounds, ); mouse_region = mouse_region - .on_drag(MouseButton::Left, { - let flexes = self.flexes.clone(); - move |drag, workspace: &mut Workspace, cx| { - let min_size = match axis { - Axis::Horizontal => HORIZONTAL_MIN_SIZE, - Axis::Vertical => VERTICAL_MIN_SIZE, - }; - // Don't allow resizing to less than the minimum size, if elements are already too small - if min_size - 1. > child_size.along(axis) - || min_size - 1. > next_child_size.along(axis) - { - return; - } - - let mut current_target_size = - (drag.position - child_start).along(axis); - - let proposed_current_pixel_change = - current_target_size - child_size.along(axis); - - if proposed_current_pixel_change < 0. { - current_target_size = f32::max(current_target_size, min_size); - } else if proposed_current_pixel_change > 0. { - // TODO: cascade this change to other children if current item is at min size - let next_target_size = f32::max( - next_child_size.along(axis) - proposed_current_pixel_change, - min_size, - ); - current_target_size = f32::min( - current_target_size, - child_size.along(axis) + next_child_size.along(axis) - - next_target_size, - ); - } - - let current_pixel_change = - current_target_size - child_size.along(axis); - let flex_change = - current_pixel_change / drag_bounds.length_along(axis); - let current_target_flex = current_flex + flex_change; - let next_target_flex = next_flex - flex_change; - - let mut borrow = flexes.borrow_mut(); - *borrow.get_mut(ix).unwrap() = current_target_flex; - *borrow.get_mut(next_ix).unwrap() = next_target_flex; - - workspace.schedule_serialize(cx); - cx.notify(); - } - }) + .on_drag( + MouseButton::Left, + Self::handle_resize( + self.flexes.clone(), + self.axis, + ix, + child_start, + visible_bounds.clone(), + ), + ) .on_click(MouseButton::Left, { let flexes = self.flexes.clone(); move |e, v: &mut Workspace, cx| { From 5fb94d39c24de148ad8fd14e5a3a13cab98ded43 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 26 Jul 2023 13:27:34 -0400 Subject: [PATCH 18/23] v0.96.x stable --- crates/zed/RELEASE_CHANNEL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/RELEASE_CHANNEL b/crates/zed/RELEASE_CHANNEL index 4de2f126df..870bbe4e50 100644 --- a/crates/zed/RELEASE_CHANNEL +++ b/crates/zed/RELEASE_CHANNEL @@ -1 +1 @@ -preview \ No newline at end of file +stable \ No newline at end of file From 4709fa658d8b505d79ccf20340b3c293635a66a2 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 26 Jul 2023 15:15:31 -0700 Subject: [PATCH 19/23] Block extra drag events in original drag handlers (#2793) In https://github.com/zed-industries/zed/pull/2790 I added an extra drag event on mouse_up which signaled the end of a drag event, as mouse_up event themselves wouldn't reliably fire if users moved their mouse too quickly. This broke the assumptions of the terminal element. This PR adds filters to all current on_drag handlers which removes this new event. Release Notes: - Fixed a bug causing terminal links to never open (preview only) - Fixed a bug in terminal link detection causing it to miss files with a `-` in it --- crates/editor/src/element.rs | 8 ++++++++ crates/gpui/src/elements/resizable.rs | 3 +++ crates/terminal/src/terminal.rs | 2 +- crates/terminal_view/src/terminal_element.rs | 4 ++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4962b08db2..6409c50666 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -168,6 +168,10 @@ impl EditorElement { .on_drag(MouseButton::Left, { let position_map = position_map.clone(); move |event, editor, cx| { + if event.end { + return; + } + if !Self::mouse_dragged( editor, event.platform_event, @@ -1207,6 +1211,10 @@ impl EditorElement { }) .on_drag(MouseButton::Left, { move |event, editor: &mut Editor, cx| { + if event.end { + return; + } + let y = event.prev_mouse_position.y(); let new_y = event.position.y(); if thumb_top < y && y < thumb_bottom { diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index da4b3473b3..73bec5521c 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -147,6 +147,9 @@ impl Element for Resizable { let max_size = side.relevant_component(constraint.max); let on_resize = self.on_resize.clone(); move |event, view: &mut V, cx| { + if event.end { + return; + } let new_size = min_size .max(prev_size + side.compute_delta(event)) .min(max_size) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index e3109102d1..06befd5f4e 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -78,7 +78,7 @@ lazy_static! { // * use more strict regex for `file://` protocol matching: original regex has `file:` inside, but we want to avoid matching `some::file::module` strings. static ref URL_REGEX: RegexSearch = RegexSearch::new(r#"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file://|git://|ssh:|ftp://)[^\u{0000}-\u{001F}\u{007F}-\u{009F}<>"\s{-}\^⟨⟩`]+"#).unwrap(); - static ref WORD_REGEX: RegexSearch = RegexSearch::new("[\\w.:/@-~]+").unwrap(); + static ref WORD_REGEX: RegexSearch = RegexSearch::new(r#"[\w.:/@\-~]+"#).unwrap(); } ///Upward flowing events, for changing the title and such diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index e29beb3ad5..194b0a9259 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -411,6 +411,10 @@ impl TerminalElement { }) // Update drag selections .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| { + if event.end { + return; + } + if cx.is_self_focused() { if let Some(conn_handle) = connection.upgrade(cx) { conn_handle.update(cx, |terminal, cx| { From 16201b722419da168eb5d4c3ce75f46360798551 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 27 Jul 2023 12:24:12 -0400 Subject: [PATCH 20/23] zed 0.96.3 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7e517cffa..ab66b071bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9454,7 +9454,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.96.2" +version = "0.96.3" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 24a6ad9a6e..18bf38122e 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.96.2" +version = "0.96.3" publish = false [lib] From eaf227b5f536855ae87cbbe20207d46d8ccf0c64 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 27 Jul 2023 19:10:01 -0600 Subject: [PATCH 21/23] Fix jumping to definition in a new file (#2803) This is broken because vim currently sets settings only on the active editor. Fix this by correcting the range on the currently active editor. It would be nice (at some point) to refactor how vim sets settings, but that's for another day. Release Notes: - vim: Fix bug when jumping to definition in new file accidentally entered visual mode. --- crates/editor/src/editor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cbf3d1a173..f194f17137 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6273,8 +6273,8 @@ impl Editor { .range .to_offset(definition.target.buffer.read(cx)); + let range = self.range_for_match(&range); if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() { - let range = self.range_for_match(&range); self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); @@ -6291,7 +6291,6 @@ impl Editor { // When selecting a definition in a different buffer, disable the nav history // to avoid creating a history entry at the previous cursor location. pane.update(cx, |pane, _| pane.disable_history()); - let range = target_editor.range_for_match(&range); target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range]); }); From eab58fe4b7dbf560fd1b7974ddc875125dfed62c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 28 Jul 2023 12:25:32 -0600 Subject: [PATCH 22/23] Don't highlight project search matches either (#2807) @JosephTLyons this is probably worth merging alongside #2803 - vim: Fix a bug where focusing project search results unexpectedly entered visual mode --- crates/editor/src/editor.rs | 2 +- crates/search/src/project_search.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f194f17137..9c188ebdc2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1526,7 +1526,7 @@ impl Editor { self.collapse_matches = collapse_matches; } - fn range_for_match(&self, range: &Range) -> Range { + pub fn range_for_match(&self, range: &Range) -> Range { if self.collapse_matches { return range.start..range.start; } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index abebb9a48f..818d5756ff 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -632,6 +632,7 @@ impl ProjectSearchView { let range_to_select = match_ranges[new_index].clone(); self.results_editor.update(cx, |editor, cx| { + let range_to_select = editor.range_for_match(&range_to_select); editor.unfold_ranges([range_to_select.clone()], false, true, cx); editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range_to_select]) @@ -673,8 +674,12 @@ impl ProjectSearchView { let is_new_search = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { if is_new_search { + let range_to_select = match_ranges + .first() + .clone() + .map(|range| editor.range_for_match(range)); editor.change_selections(Some(Autoscroll::fit()), cx, |s| { - s.select_ranges(match_ranges.first().cloned()) + s.select_ranges(range_to_select) }); } editor.highlight_background::( From d6a72f17d1b1f25ecbf624669fcb579602fd54b2 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 28 Jul 2023 18:31:58 -0400 Subject: [PATCH 23/23] zed 0.96.4 --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab66b071bd..8b979761fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9454,7 +9454,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.96.3" +version = "0.96.4" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 18bf38122e..6f31134a0b 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.96.3" +version = "0.96.4" publish = false [lib]